/*============================================================================*/
/*

NewFATFS <https://github.com/Ibavaresco/NewFATFS>

FAT File System routines based on Chan's FatFs (<http://elm-chan.org/fsw/ff/>).
Please see his original copyright and license further below.
--------------------------------------------------------------------------------
Copyright (c)2022-2023, Isaac Marino Bavaresco (isaacbavaresco@yahoo.com.br)

 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are met:
     * Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer.
     * Neither the name of the author nor the
       names of its contributors may be used to endorse or promote products
       derived from this software without specific prior written permission.

 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY
 EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------

Although most of the code is still Chan's, this version has extensive changes
to its structure and API.

The main goal was to implement the most POSIX-compliant version possible, so
we could reuse code from desktop applications without (or with minimal) changes.

Another goal was to integrate seamlessly with NewLib, which I think was
acomplished very well.

The original version has a lot of issues, including security related, but the
most annoying one is its weird API.

In 2005 I wrote a FLASH file system for a commercial product and it proved to
be very good, although only suitable for embedded memories. In 2020 the product's
lifecycle was (long) past, then I published it on GitHub:
(<https://github.com/Ibavaresco/FileSystem/tree/master>)

In 2013 I started development of a new product that needed SD-Card support. The
selected MCU was an Atmel ARM, and the application automatically generated by
ASF included Chan's FatFs. It annoyed me that its API was so weird, so I wrote
a (more or less) POSIX compatibility wrapper layer
(<http://www.piclist.com/techref/member/IMB-yahoo-J86/Atmel-s-FatFS-Wrapper.htm>)

Later I re-wrote it in a NewLib-compatible format. It was published on
<https://spaces.atmel.com/gf/project/posixfatfs>, but now it is not accesible
anymore.

Now it is time to go all the way and create a native version without adaptation
layers, and in the process fix a lot of other issues.

First I "maximized" the configuration. That is, I removed all the options that
reduced features (FF_FS_READONLY, FF_FS_MINIMIZE, FF_FS_TINY, etc.) and enabled
all the options that included features (FF_FS_EXFAT, FF_USE_LFN, etc.).

Then I removed all the conditional compilation directives and replaced several
macros with their values. Also, I replaced several "magic-numbers" spread all
over the code. There are still a lot of them that I plan to replace as time goes
by.

A code re-formatter (Astyle) was used to change the style to Whitesmiths and fix
a lot of unreadable and confusing formatting.

I noticed that the way he uses the semaphores/mutexes was not safe enough and
left some critical parts unprotected from race conditions, so I changed it.
I chose a simple solution of a single mutex for all the filesystem, but that may
not be ideal for some configurations and I plan to add more options in the future.
I think that the ideal solution is comprised of a global mutex to protect the
accesses to filesystem-wide objects for a short time and one mutex for each volume
or physical drive (that will depend on whether the physical drive layer can
support concurrency or not).

Another point that I think he got wrong was the necessity for several LFN buffers.
In my analysis, it seems that the LFN buffers will never be used concurrently,
because they are always initialized only after the filesystem is locked and
released before the filesystem is unlocked. Besides, there is just one pointer in
the 'FATFS' struct. I'm still trying to find some condition that requires several
buffers, but for now I'm using one static buffer in each 'ffs_volume_t' struct.

The locking mechanism is another complicated point. I chose a different approach
of two or threee function layers, to simplify the logic and provide some sort of
"finally" feature.

I also changed most of the types and macros names, to provide a more homogeneous
approach and require less memorization. I plan to change many of the variables and
field names too, because most of them doesn't make much sense to me.

I used a different way of loading physical drivers, based on code from STM, but
with my personal touch. Also, I totally changed the way volumes are mounted and
unmonted, because I never managed the original way to work correctly. If I unmounted
a volume, I could not manage to mount it again without a reboot.

As the new file system is POSIX and NewLib compatible, we can use all the resources
of the C library, including bufferd files and the functions that work with them
(fprintf, fscanf, etc.). That rendered useless several of the FatFs functions
(f_gets, f_puts, f_printf).


WARNING: Although I tested most of the code, it is still under development. I am
publishing it now to allow for others to try it and help in the testing and
perhaps in the development too.

*/
/*============================================================================*/

/*  ----------------------------------------------------------------------------/
    /  FatFs - Generic FAT Filesystem Module  R0.15 w/patch1                      /
    /-----------------------------------------------------------------------------/
    /
    / Copyright (C) 2022, ChaN, all right reserved.
    /
    / FatFs module is an open source software. Redistribution and use of FatFs in
    / source and binary forms, with or without modification, are permitted provided
    / that the following condition is met:
    /
    / 1. Redistributions of source code must retain the above copyright notice,
    /    this condition and the following disclaimer.
    /
    / This software is provided by the copyright holder and contributors "AS IS"
    / and any warranties related to this software are DISCLAIMED.
    / The copyright owner or contributors be NOT LIABLE for any damages caused
    / by use of this software.
    /
    /----------------------------------------------------------------------------*/

/*============================================================================*/
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <fcntl.h>
#include <unistd.h>
#include "ffs.h"			/* Declarations of FatFs API */
#include "ffs_syscalls.h"
#include "ffs_diskio.h"
/*  --------------------------------------------------------------------------

    Module Private Definitions

    ---------------------------------------------------------------------------*/

/* Limits and boundaries */
#define MAX_DIR		0x200000		/* Max size of FAT directory */
#define MAX_DIR_EX	0x10000000		/* Max size of exFAT directory */
#define MAX_FAT12	0xFF5			/* Max FAT12 clusters (differs from specs, but right for real DOS/Windows behavior) */
#define MAX_FAT16	0xFFF5			/* Max FAT16 clusters (differs from specs, but right for real DOS/Windows behavior) */
#define MAX_FAT32	0x0FFFFFF5		/* Max FAT32 clusters (not specified, practical limit) */
#define MAX_EXFAT	0x7FFFFFFD		/* Max exFAT clusters (differs from specs, implementation limit) */


/* Character code support macros */
#define IsUpper(c)		((c) >= 'A' && (c) <= 'Z')
#define IsLower(c)		((c) >= 'a' && (c) <= 'z')
#define IsDigit(c)		((c) >= '0' && (c) <= '9')
#define IsSeparator(c)	((c) == '/' || (c) == '\\')
#define IsTerminator(c)	((unsigned int)(c) < ' ')
#define IsSurrogate(c)	((c) >= 0xD800 && (c) <= 0xDFFF)
#define IsSurrogateH(c)	((c) >= 0xD800 && (c) <= 0xDBFF)
#define IsSurrogateL(c)	((c) >= 0xDC00 && (c) <= 0xDFFF)


/* Additional file access control and file status flags for internal use */
#define FA_MODIFIED	0x40	/* File has been modified */
#define FA_DIRTY	0x80	/* ffs_file_t.buf[] needs to be written-back */


/* Additional file attribute bits for internal use */
#define AM_VOL		0x08	/* Volume label */
#define AM_LFN		0x0F	/* LFN entry */
#define AM_MASK		0x3F	/* Mask of defined bits in FAT */
#define AM_MASKX	0x37	/* Mask of defined bits in exFAT */


/* Name status flags in fn[11] */
#define NSFLAG		11		/* Index of the name status byte */
#define NS_LOSS		0x01	/* Out of 8.3 format */
#define NS_LFN		0x02	/* Force to create LFN entry */
#define NS_LAST		0x04	/* Last segment */
#define NS_BODY		0x08	/* Lower case flag (body) */
#define NS_EXT		0x10	/* Lower case flag (ext) */
#define NS_DOT		0x20	/* Dot entry */
#define NS_NOLFN	0x40	/* Do not find LFN */
#define NS_NONAME	0x80	/* Not followed */


/* exFAT directory entry types */
#define	ET_BITMAP	0x81	/* Allocation bitmap */
#define	ET_UPCASE	0x82	/* Up-case table */
#define	ET_VLABEL	0x83	/* Volume label */
#define	ET_FILEDIR	0x85	/* File and directory */
#define	ET_STREAM	0xC0	/* Stream extension */
#define	ET_FILENAME	0xC1	/* Name extension */


/*  FatFs refers the FAT structure as simple byte array instead of structure member
    / because the C structure is not binary compatible between different platforms */

#define BS_JmpBoot			0		/* x86 jump instruction (3-byte) */
#define BS_OEMName			3		/* OEM name (8-byte) */
#define BPB_BytsPerSec		11		/* Sector size [byte] (uint16_t) */
#define BPB_SecPerClus		13		/* Cluster size [sector] (uint8_t) */
#define BPB_RsvdSecCnt		14		/* Size of reserved area [sector] (uint16_t) */
#define BPB_NumFATs			16		/* Number of FATs (uint8_t) */
#define BPB_RootEntCnt		17		/* Size of root directory area for FAT [entry] (uint16_t) */
#define BPB_TotSec16		19		/* Volume size (16-bit) [sector] (uint16_t) */
#define BPB_Media			21		/* Media descriptor byte (uint8_t) */
#define BPB_FATSz16			22		/* FAT size (16-bit) [sector] (uint16_t) */
#define BPB_SecPerTrk		24		/* Number of sectors per track for int13h [sector] (uint16_t) */
#define BPB_NumHeads		26		/* Number of heads for int13h (uint16_t) */
#define BPB_HiddSec			28		/* Volume offset from top of the drive (uint32_t) */
#define BPB_TotSec32		32		/* Volume size (32-bit) [sector] (uint32_t) */
#define BS_DrvNum			36		/* Physical drive number for int13h (uint8_t) */
#define BS_NTres			37		/* WindowsNT error flag (uint8_t) */
#define BS_BootSig			38		/* Extended boot signature (uint8_t) */
#define BS_VolID			39		/* Volume serial number (uint32_t) */
#define BS_VolLab			43		/* Volume label string (8-byte) */
#define BS_FilSysType		54		/* Filesystem type string (8-byte) */
#define BS_BootCode			62		/* Boot code (448-byte) */
#define BS_55AA				510		/* Signature word (uint16_t) */

#define BPB_FATSz32			36		/* FAT32: FAT size [sector] (uint32_t) */
#define BPB_ExtFlags32		40		/* FAT32: Extended flags (uint16_t) */
#define BPB_FSVer32			42		/* FAT32: Filesystem version (uint16_t) */
#define BPB_RootClus32		44		/* FAT32: Root directory cluster (uint32_t) */
#define BPB_FSInfo32		48		/* FAT32: Offset of FSINFO sector (uint16_t) */
#define BPB_BkBootSec32		50		/* FAT32: Offset of backup boot sector (uint16_t) */
#define BS_DrvNum32			64		/* FAT32: Physical drive number for int13h (uint8_t) */
#define BS_NTres32			65		/* FAT32: Error flag (uint8_t) */
#define BS_BootSig32		66		/* FAT32: Extended boot signature (uint8_t) */
#define BS_VolID32			67		/* FAT32: Volume serial number (uint32_t) */
#define BS_VolLab32			71		/* FAT32: Volume label string (8-byte) */
#define BS_FilSysType32		82		/* FAT32: Filesystem type string (8-byte) */
#define BS_BootCode32		90		/* FAT32: Boot code (420-byte) */

#define BPB_ZeroedEx		11		/* exFAT: MBZ field (53-byte) */
#define BPB_VolOfsEx		64		/* exFAT: Volume offset from top of the drive [sector] (uint64_t) */
#define BPB_TotSecEx		72		/* exFAT: Volume size [sector] (uint64_t) */
#define BPB_FatOfsEx		80		/* exFAT: FAT offset from top of the volume [sector] (uint32_t) */
#define BPB_FatSzEx			84		/* exFAT: FAT size [sector] (uint32_t) */
#define BPB_DataOfsEx		88		/* exFAT: Data offset from top of the volume [sector] (uint32_t) */
#define BPB_NumClusEx		92		/* exFAT: Number of clusters (uint32_t) */
#define BPB_RootClusEx		96		/* exFAT: Root directory start cluster (uint32_t) */
#define BPB_VolIDEx			100		/* exFAT: Volume serial number (uint32_t) */
#define BPB_FSVerEx			104		/* exFAT: Filesystem version (uint16_t) */
#define BPB_VolFlagEx		106		/* exFAT: Volume flags (uint16_t) */
#define BPB_BytsPerSecEx	108		/* exFAT: Log2 of sector size in unit of byte (uint8_t) */
#define BPB_SecPerClusEx	109		/* exFAT: Log2 of cluster size in unit of sector (uint8_t) */
#define BPB_NumFATsEx		110		/* exFAT: Number of FATs (uint8_t) */
#define BPB_DrvNumEx		111		/* exFAT: Physical drive number for int13h (uint8_t) */
#define BPB_PercInUseEx		112		/* exFAT: Percent in use (uint8_t) */
#define BPB_RsvdEx			113		/* exFAT: Reserved (7-byte) */
#define BS_BootCodeEx		120		/* exFAT: Boot code (390-byte) */

#define DIR_Name			0		/* Short file name (11-byte) */
#define DIR_Attr			11		/* Attribute (uint8_t) */
#define DIR_NTres			12		/* Lower case flag (uint8_t) */
#define DIR_CrtTime10		13		/* Created time sub-second (uint8_t) */
#define DIR_CrtTime			14		/* Created time (uint32_t) */
#define DIR_LstAccDate		18		/* Last accessed date (uint16_t) */
#define DIR_FstClusHI		20		/* Higher 16-bit of first cluster (uint16_t) */
#define DIR_ModTime			22		/* Modified time (uint32_t) */
#define DIR_FstClusLO		26		/* Lower 16-bit of first cluster (uint16_t) */
#define DIR_FileSize		28		/* File size (uint32_t) */
#define LDIR_Ord			0		/* LFN: LFN order and LLE flag (uint8_t) */
#define LDIR_Attr			11		/* LFN: LFN attribute (uint8_t) */
#define LDIR_Type			12		/* LFN: Entry type (uint8_t) */
#define LDIR_Chksum			13		/* LFN: Checksum of the SFN (uint8_t) */
#define LDIR_FstClusLO		26		/* LFN: MBZ field (uint16_t) */
#define XDIR_Type			0		/* exFAT: Type of exFAT directory entry (uint8_t) */
#define XDIR_NumLabel		1		/* exFAT: Number of volume label characters (uint8_t) */
#define XDIR_Label			2		/* exFAT: Volume label (11-uint16_t) */
#define XDIR_CaseSum		4		/* exFAT: Sum of case conversion table (uint32_t) */
#define XDIR_NumSec			1		/* exFAT: Number of secondary entries (uint8_t) */
#define XDIR_SetSum			2		/* exFAT: Sum of the set of directory entries (uint16_t) */
#define XDIR_Attr			4		/* exFAT: File attribute (uint16_t) */
#define XDIR_CrtTime		8		/* exFAT: Created time (uint32_t) */
#define XDIR_ModTime		12		/* exFAT: Modified time (uint32_t) */
#define XDIR_AccTime		16		/* exFAT: Last accessed time (uint32_t) */
#define XDIR_CrtTime10		20		/* exFAT: Created time subsecond (uint8_t) */
#define XDIR_ModTime10		21		/* exFAT: Modified time subsecond (uint8_t) */
#define XDIR_CrtTZ			22		/* exFAT: Created timezone (uint8_t) */
#define XDIR_ModTZ			23		/* exFAT: Modified timezone (uint8_t) */
#define XDIR_AccTZ			24		/* exFAT: Last accessed timezone (uint8_t) */
#define XDIR_GenFlags		33		/* exFAT: General secondary flags (uint8_t) */
#define XDIR_NumName		35		/* exFAT: Number of file name characters (uint8_t) */
#define XDIR_NameHash		36		/* exFAT: Hash of file name (uint16_t) */
#define XDIR_ValidFileSize	40		/* exFAT: Valid file size (uint64_t) */
#define XDIR_FstClus		52		/* exFAT: First cluster of the file data (uint32_t) */
#define XDIR_FileSize		56		/* exFAT: File/Directory size (uint64_t) */

#define SIZE_OF_DIR_ENTRY	32		/* Size of a directory entry */
#define DDEM				0xE5	/* Deleted directory entry mark set to DIR_Name[0] */
#define RDDEM				0x05	/* Replacement of the character collides with DDEM */
#define LLEF				0x40	/* Last long entry flag in LDIR_Ord */

#define FSI_LeadSig			0		/* FAT32 FSI: Leading signature (uint32_t) */
#define FSI_StrucSig		484		/* FAT32 FSI: Structure signature (uint32_t) */
#define FSI_Free_Count		488		/* FAT32 FSI: Number of free clusters (uint32_t) */
#define FSI_Nxt_Free		492		/* FAT32 FSI: Last allocated cluster (uint32_t) */

#define MBR_Table			446		/* MBR: Offset of partition table in the MBR */
#define SZ_PTE				16		/* MBR: Size of a partition table entry */
#define PTE_Boot			0		/* MBR PTE: Boot indicator */
#define PTE_StHead			1		/* MBR PTE: Start head */
#define PTE_StSec			2		/* MBR PTE: Start sector */
#define PTE_StCyl			3		/* MBR PTE: Start cylinder */
#define PTE_System			4		/* MBR PTE: System ID */
#define PTE_EdHead			5		/* MBR PTE: End head */
#define PTE_EdSec			6		/* MBR PTE: End sector */
#define PTE_EdCyl			7		/* MBR PTE: End cylinder */
#define PTE_StLba			8		/* MBR PTE: Start in LBA */
#define PTE_SizLba			12		/* MBR PTE: Size in LBA */

#define GPTH_Sign			0		/* GPT HDR: Signature (8-byte) */
#define GPTH_Rev			8		/* GPT HDR: Revision (uint32_t) */
#define GPTH_Size			12		/* GPT HDR: Header size (uint32_t) */
#define GPTH_Bcc			16		/* GPT HDR: Header BCC (uint32_t) */
#define GPTH_CurLba			24		/* GPT HDR: This header LBA (uint64_t) */
#define GPTH_BakLba			32		/* GPT HDR: Another header LBA (uint64_t) */
#define GPTH_FstLba			40		/* GPT HDR: First LBA for partition data (uint64_t) */
#define GPTH_LstLba			48		/* GPT HDR: Last LBA for partition data (uint64_t) */
#define GPTH_DskGuid		56		/* GPT HDR: Disk GUID (16-byte) */
#define GPTH_PtOfs			72		/* GPT HDR: Partition table LBA (uint64_t) */
#define GPTH_PtNum			80		/* GPT HDR: Number of table entries (uint32_t) */
#define GPTH_PteSize		84		/* GPT HDR: Size of table entry (uint32_t) */
#define GPTH_PtBcc			88		/* GPT HDR: Partition table BCC (uint32_t) */
#define SZ_GPTE				128		/* GPT PTE: Size of partition table entry */
#define GPTE_PtGuid			0		/* GPT PTE: Partition type GUID (16-byte) */
#define GPTE_UpGuid			16		/* GPT PTE: Partition unique GUID (16-byte) */
#define GPTE_FstLba			32		/* GPT PTE: First LBA of partition (uint64_t) */
#define GPTE_LstLba			40		/* GPT PTE: Last LBA of partition (uint64_t) */
#define GPTE_Flags			48		/* GPT PTE: Partition flags (uint64_t) */
#define GPTE_Name			56		/* GPT PTE: Partition name */

/* Definitions of sector size */
#if (FFS_CONFIG_MAXIMUMSECTORSIZE > FFS_CONFIG_MINIMUMSECTORSIZE) \
	|| (FFS_CONFIG_MAXIMUMSECTORSIZE != 512 && FFS_CONFIG_MAXIMUMSECTORSIZE != 1024 \
	&& FFS_CONFIG_MAXIMUMSECTORSIZE != 2048 && FFS_CONFIG_MAXIMUMSECTORSIZE != 4096) \
	|| (FFS_CONFIG_MINIMUMSECTORSIZE != 512 && FFS_CONFIG_MINIMUMSECTORSIZE != 1024 && \
	FFS_CONFIG_MINIMUMSECTORSIZE != 2048 && FFS_CONFIG_MINIMUMSECTORSIZE != 4096)
#error Wrong sector size configuration
#endif

/* SBCS up-case tables (\x80-\xFF) */
#define TBL_CT437  {0x80,0x9A,0x45,0x41,0x8E,0x41,0x8F,0x80,0x45,0x45,0x45,0x49,0x49,0x49,0x8E,0x8F, \
					0x90,0x92,0x92,0x4F,0x99,0x4F,0x55,0x55,0x59,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \
					0x41,0x49,0x4F,0x55,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
					0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
					0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
					0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
					0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \
					0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
#define TBL_CT720  {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \
					0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \
					0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
					0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
					0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
					0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
					0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \
					0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
#define TBL_CT737  {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \
					0x90,0x92,0x92,0x93,0x94,0x95,0x96,0x97,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87, \
					0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x90,0x91,0xAA,0x92,0x93,0x94,0x95,0x96, \
					0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
					0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
					0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
					0x97,0xEA,0xEB,0xEC,0xE4,0xED,0xEE,0xEF,0xF5,0xF0,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \
					0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
#define TBL_CT771  {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \
					0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \
					0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \
					0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
					0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
					0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDC,0xDE,0xDE, \
					0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \
					0xF0,0xF0,0xF2,0xF2,0xF4,0xF4,0xF6,0xF6,0xF8,0xF8,0xFA,0xFA,0xFC,0xFC,0xFE,0xFF}
#define TBL_CT775  {0x80,0x9A,0x91,0xA0,0x8E,0x95,0x8F,0x80,0xAD,0xED,0x8A,0x8A,0xA1,0x8D,0x8E,0x8F, \
					0x90,0x92,0x92,0xE2,0x99,0x95,0x96,0x97,0x97,0x99,0x9A,0x9D,0x9C,0x9D,0x9E,0x9F, \
					0xA0,0xA1,0xE0,0xA3,0xA3,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
					0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
					0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
					0xB5,0xB6,0xB7,0xB8,0xBD,0xBE,0xC6,0xC7,0xA5,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
					0xE0,0xE1,0xE2,0xE3,0xE5,0xE5,0xE6,0xE3,0xE8,0xE8,0xEA,0xEA,0xEE,0xED,0xEE,0xEF, \
					0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
#define TBL_CT850  {0x43,0x55,0x45,0x41,0x41,0x41,0x41,0x43,0x45,0x45,0x45,0x49,0x49,0x49,0x41,0x41, \
					0x45,0x92,0x92,0x4F,0x4F,0x4F,0x55,0x55,0x59,0x4F,0x55,0x4F,0x9C,0x4F,0x9E,0x9F, \
					0x41,0x49,0x4F,0x55,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
					0xB0,0xB1,0xB2,0xB3,0xB4,0x41,0x41,0x41,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
					0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0x41,0x41,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
					0xD1,0xD1,0x45,0x45,0x45,0x49,0x49,0x49,0x49,0xD9,0xDA,0xDB,0xDC,0xDD,0x49,0xDF, \
					0x4F,0xE1,0x4F,0x4F,0x4F,0x4F,0xE6,0xE8,0xE8,0x55,0x55,0x55,0x59,0x59,0xEE,0xEF, \
					0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
#define TBL_CT852  {0x80,0x9A,0x90,0xB6,0x8E,0xDE,0x8F,0x80,0x9D,0xD3,0x8A,0x8A,0xD7,0x8D,0x8E,0x8F, \
					0x90,0x91,0x91,0xE2,0x99,0x95,0x95,0x97,0x97,0x99,0x9A,0x9B,0x9B,0x9D,0x9E,0xAC, \
					0xB5,0xD6,0xE0,0xE9,0xA4,0xA4,0xA6,0xA6,0xA8,0xA8,0xAA,0x8D,0xAC,0xB8,0xAE,0xAF, \
					0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBD,0xBF, \
					0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC6,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
					0xD1,0xD1,0xD2,0xD3,0xD2,0xD5,0xD6,0xD7,0xB7,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
					0xE0,0xE1,0xE2,0xE3,0xE3,0xD5,0xE6,0xE6,0xE8,0xE9,0xE8,0xEB,0xED,0xED,0xDD,0xEF, \
					0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xEB,0xFC,0xFC,0xFE,0xFF}
#define TBL_CT855  {0x81,0x81,0x83,0x83,0x85,0x85,0x87,0x87,0x89,0x89,0x8B,0x8B,0x8D,0x8D,0x8F,0x8F, \
					0x91,0x91,0x93,0x93,0x95,0x95,0x97,0x97,0x99,0x99,0x9B,0x9B,0x9D,0x9D,0x9F,0x9F, \
					0xA1,0xA1,0xA3,0xA3,0xA5,0xA5,0xA7,0xA7,0xA9,0xA9,0xAB,0xAB,0xAD,0xAD,0xAE,0xAF, \
					0xB0,0xB1,0xB2,0xB3,0xB4,0xB6,0xB6,0xB8,0xB8,0xB9,0xBA,0xBB,0xBC,0xBE,0xBE,0xBF, \
					0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC7,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
					0xD1,0xD1,0xD3,0xD3,0xD5,0xD5,0xD7,0xD7,0xDD,0xD9,0xDA,0xDB,0xDC,0xDD,0xE0,0xDF, \
					0xE0,0xE2,0xE2,0xE4,0xE4,0xE6,0xE6,0xE8,0xE8,0xEA,0xEA,0xEC,0xEC,0xEE,0xEE,0xEF, \
					0xF0,0xF2,0xF2,0xF4,0xF4,0xF6,0xF6,0xF8,0xF8,0xFA,0xFA,0xFC,0xFC,0xFD,0xFE,0xFF}
#define TBL_CT857  {0x80,0x9A,0x90,0xB6,0x8E,0xB7,0x8F,0x80,0xD2,0xD3,0xD4,0xD8,0xD7,0x49,0x8E,0x8F, \
					0x90,0x92,0x92,0xE2,0x99,0xE3,0xEA,0xEB,0x98,0x99,0x9A,0x9D,0x9C,0x9D,0x9E,0x9E, \
					0xB5,0xD6,0xE0,0xE9,0xA5,0xA5,0xA6,0xA6,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
					0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
					0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC7,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
					0xD0,0xD1,0xD2,0xD3,0xD4,0x49,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
					0xE0,0xE1,0xE2,0xE3,0xE5,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xDE,0xED,0xEE,0xEF, \
					0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
#define TBL_CT860  {0x80,0x9A,0x90,0x8F,0x8E,0x91,0x86,0x80,0x89,0x89,0x92,0x8B,0x8C,0x98,0x8E,0x8F, \
					0x90,0x91,0x92,0x8C,0x99,0xA9,0x96,0x9D,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \
					0x86,0x8B,0x9F,0x96,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
					0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
					0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
					0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
					0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \
					0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
#define TBL_CT861  {0x80,0x9A,0x90,0x41,0x8E,0x41,0x8F,0x80,0x45,0x45,0x45,0x8B,0x8B,0x8D,0x8E,0x8F, \
					0x90,0x92,0x92,0x4F,0x99,0x8D,0x55,0x97,0x97,0x99,0x9A,0x9D,0x9C,0x9D,0x9E,0x9F, \
					0xA4,0xA5,0xA6,0xA7,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
					0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
					0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
					0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
					0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \
					0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
#define TBL_CT862  {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \
					0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \
					0x41,0x49,0x4F,0x55,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
					0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
					0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
					0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
					0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \
					0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
#define TBL_CT863  {0x43,0x55,0x45,0x41,0x41,0x41,0x86,0x43,0x45,0x45,0x45,0x49,0x49,0x8D,0x41,0x8F, \
					0x45,0x45,0x45,0x4F,0x45,0x49,0x55,0x55,0x98,0x4F,0x55,0x9B,0x9C,0x55,0x55,0x9F, \
					0xA0,0xA1,0x4F,0x55,0xA4,0xA5,0xA6,0xA7,0x49,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
					0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
					0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
					0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
					0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \
					0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
#define TBL_CT864  {0x80,0x9A,0x45,0x41,0x8E,0x41,0x8F,0x80,0x45,0x45,0x45,0x49,0x49,0x49,0x8E,0x8F, \
					0x90,0x92,0x92,0x4F,0x99,0x4F,0x55,0x55,0x59,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \
					0x41,0x49,0x4F,0x55,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
					0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
					0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
					0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
					0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \
					0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
#define TBL_CT865  {0x80,0x9A,0x90,0x41,0x8E,0x41,0x8F,0x80,0x45,0x45,0x45,0x49,0x49,0x49,0x8E,0x8F, \
					0x90,0x92,0x92,0x4F,0x99,0x4F,0x55,0x55,0x59,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \
					0x41,0x49,0x4F,0x55,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
					0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
					0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
					0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
					0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF, \
					0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
#define TBL_CT866  {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \
					0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \
					0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \
					0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
					0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
					0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \
					0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \
					0xF0,0xF0,0xF2,0xF2,0xF4,0xF4,0xF6,0xF6,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF}
#define TBL_CT869  {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, \
					0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x86,0x9C,0x8D,0x8F,0x90, \
					0x91,0x90,0x92,0x95,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, \
					0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \
					0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, \
					0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xA4,0xA5,0xA6,0xD9,0xDA,0xDB,0xDC,0xA7,0xA8,0xDF, \
					0xA9,0xAA,0xAC,0xAD,0xB5,0xB6,0xB7,0xB8,0xBD,0xBE,0xC6,0xC7,0xCF,0xCF,0xD0,0xEF, \
					0xF0,0xF1,0xD1,0xD2,0xD3,0xF5,0xD4,0xF7,0xF8,0xF9,0xD5,0x96,0x95,0x98,0xFE,0xFF}


/* DBCS code range |----- 1st byte -----|  |----------- 2nd byte -----------| */
/*                  <------>    <------>    <------>    <------>    <------>  */
#define TBL_DC932 {0x81, 0x9F, 0xE0, 0xFC, 0x40, 0x7E, 0x80, 0xFC, 0x00, 0x00}
#define TBL_DC936 {0x81, 0xFE, 0x00, 0x00, 0x40, 0x7E, 0x80, 0xFE, 0x00, 0x00}
#define TBL_DC949 {0x81, 0xFE, 0x00, 0x00, 0x41, 0x5A, 0x61, 0x7A, 0x81, 0xFE}
#define TBL_DC950 {0x81, 0xFE, 0x00, 0x00, 0x40, 0x7E, 0xA1, 0xFE, 0x00, 0x00}


/* Macros for table definitions */
#define MERGE_2STR(a, b) a ## b
#define MKCVTBL(hd, cp) MERGE_2STR(hd, cp)




/*  --------------------------------------------------------------------------

    Module Private Work Area

    ---------------------------------------------------------------------------*/
/*  Remark: Variables defined here without initial value shall be guaranteed
    /  zero/null at start-up. If not, the linker option or start-up routine is
    /  not compliance with C standard. */

/*--------------------------------*/
/* File/Volume controls           */
/*--------------------------------*/

#if FFS_CONFIG_MAXVOLUMES < 1 || FFS_CONFIG_MAXVOLUMES > 10
#error Wrong FFS_CONFIG_MAXVOLUMES setting
#endif
/*============================================================================*/
#if FFS_CONFIG_MINGPT > 0x100000000
#error Wrong FFS_CONFIG_MINGPT setting
#endif
static const uint8_t GUID_MS_Basic[16] = {0xA2, 0xA0, 0xD0, 0xEB, 0xE5, 0xB9, 0x33, 0x44, 0x87, 0xC0, 0x68, 0xB6, 0xB7, 0x26, 0x99, 0xC7};


/*--------------------------------*/
/* LFN/Directory working buffer   */
/*--------------------------------*/

#if FFS_CONFIG_MAXIMUMLFN < 12 || FFS_CONFIG_MAXIMUMLFN > 255
#error Wrong setting of FFS_CONFIG_MAXIMUMLFN
#endif
#if FFS_CONFIG_MAXIMUMLFN < FFS_SFNBUFSIZE || FFS_SFNBUFSIZE < 12
#error Wrong setting of FFS_CONFIG_MAXIMUMLFN or FFS_SFNBUFSIZE
#endif
#if FFS_CONFIG_LFNUNICODE < 0 || FFS_CONFIG_LFNUNICODE > 3
#error Wrong setting of FFS_CONFIG_LFNUNICODE
#endif
static const uint8_t LfnOfs[] = {1, 3, 5, 7, 9, 14, 16, 18, 20, 22, 24, 28, 30};	/* FAT: Offset of LFN characters in the directory entry */
#define MAXDIRB(nc)	((nc + 44U) / 15 * SIZE_OF_DIR_ENTRY)	/* exFAT: Size of directory entry block scratchpad buffer needed for the name length */

/*--------------------------------*/
/* Code conversion tables         */
/*--------------------------------*/

static uint16_t			FFS_CodePage;	/* Current code page */
static const uint8_t	*FFS_ExCvt;		/* Ptr to SBCS up-case table Ct???[] (null:not used) */
static const uint8_t	*FFS_DbcTbl;	/* Ptr to DBCS code range table Dc???[] (null:not used) */

static const uint8_t Ct437[] = TBL_CT437;
static const uint8_t Ct720[] = TBL_CT720;
static const uint8_t Ct737[] = TBL_CT737;
static const uint8_t Ct771[] = TBL_CT771;
static const uint8_t Ct775[] = TBL_CT775;
static const uint8_t Ct850[] = TBL_CT850;
static const uint8_t Ct852[] = TBL_CT852;
static const uint8_t Ct855[] = TBL_CT855;
static const uint8_t Ct857[] = TBL_CT857;
static const uint8_t Ct860[] = TBL_CT860;
static const uint8_t Ct861[] = TBL_CT861;
static const uint8_t Ct862[] = TBL_CT862;
static const uint8_t Ct863[] = TBL_CT863;
static const uint8_t Ct864[] = TBL_CT864;
static const uint8_t Ct865[] = TBL_CT865;
static const uint8_t Ct866[] = TBL_CT866;
static const uint8_t Ct869[] = TBL_CT869;
static const uint8_t Dc932[] = TBL_DC932;
static const uint8_t Dc936[] = TBL_DC936;
static const uint8_t Dc949[] = TBL_DC949;
static const uint8_t Dc950[] = TBL_DC950;
/*============================================================================*/
#define FFS_FILESYSTEMTYPE_INVALID	0
#define FFS_FILESYSTEMTYPE_FAT12	1
#define FFS_FILESYSTEMTYPE_FAT16	2
#define FFS_FILESYSTEMTYPE_FAT32	3
#define FFS_FILESYSTEMTYPE_EXFAT	4
/*============================================================================*/
/* Filesystem object structure (FATFS) */
/*============================================================================*/
typedef struct
	{
	lba_t				volbase;				/* Volume base sector */
	lba_t				fatbase;				/* FAT base sector */
	lba_t				dirbase;				/* Root directory base sector (FAT12/16) or cluster (FAT32/exFAT) */
	lba_t				database;				/* Data base sector */
	lba_t				bitbase;				/* Allocation bitmap base sector */
	lba_t				sectorinwin;			/* Current sector appearing in the win[] */
	ffs_char_t			volname[8];
	ffs_wchar_t			lfnbuf[FFS_CONFIG_MAXIMUMLFN + 1];			/* LFN working buffer */
	uint8_t				dirbuf[MAXDIRB( FFS_CONFIG_MAXIMUMLFN )];	/* Directory entry block scratchpad buffer for exFAT */
	uint32_t			curdirstartcluster;		/* Current directory start cluster (0:root) */
	uint32_t			parentdirstartcluster;	/* Containing directory start cluster (invalid when curdirstartcluster is 0) */
	uint32_t			parentdirsize;			/* b31-b8:Size of containing directory, b7-b0: Chain status */
	uint32_t			offsetinparentdir;		/* Offset in the containing directory (invalid when curdirstartcluster is 0) */
	uint32_t			fatentriescount;		/* Number of FAT entries (number of clusters + 2) */
	uint32_t			sectorsperfat;			/* Number of sectors per FAT */
	uint32_t			last_clst;				/* Last allocated cluster */
	uint32_t			freeclusterscount;		/* Number of free clusters */
	int					partition;				/* Partition number. Invalid if equals to -1. */
	int					nextidx;
	uint16_t			mountid;				/* Volume mount ID */
	uint16_t			rootdirentriescount;	/* Number of root directory entries (FAT12/16) */
	uint16_t			sectorspercluster;		/* Cluster size [sectors] */
	uint16_t			bytespersector;			/* Sector size (512, 1024, 2048 or 4096) */
	uint8_t				fs_type;				/* Filesystem type (0:not mounted) */
	uint8_t				pdrv;					/* Volume hosting physical drive */
	uint8_t				ldrv;					/* Logical drive number (used only when FF_FS_REENTRANT) */
	uint8_t				fatcopies;				/* Number of FATs (1 or 2) */
	uint8_t				windowisdirty;			/* win[] status (b0:dirty) */
	uint8_t				fsi_flag;				/* FSINFO status (b7:disabled, b0:dirty) */
	uint8_t				win[FFS_CONFIG_MAXIMUMSECTORSIZE];	/* Disk access window for Directory, FAT (and file data at tiny cfg) */
	} ffs_volume_t;
/*============================================================================*/
/* File lock controls */
/*============================================================================*/
typedef struct
	{
	lba_t				sectorofdirentry;		/* Sector number containing the directory entry (not used at exFAT) */
	lba_t				sectorinbuf;			/* Sector number appearing in buf[] (0:invalid) */
	filesize_t			sizebytes;				/* Object size (valid when startcluster != 0) */
	ffs_volume_t		*fs;					/* Object ID 1, volume (NULL:blank entry) */
	uint8_t				*ptrtodirentry;			/* Pointer to the directory entry in the win[] (not used at exFAT) */
	uint32_t			startcluster;			/* Object data start cluster (0:no cluster or root directory) */
	uint32_t			parentdirstartcluster;	/* Object ID 2, containing directory (0:root) */
	uint32_t			offsetinparentdir;		/* Object ID 3, offset in the directory */
	uint32_t			parentdirsize;			/* b31-b8:Size of containing directory, b7-b0: Chain status (valid when parentdirstartcluster != 0) */
	uint32_t			sizeoffirstfrag;		/* Size of first fragment - 1 (valid when chainstatus == 3) */
	uint32_t			sizeoflastfrag;			/* Size of last fragment needs to be written to FAT (valid when not zero) */
	int					nextidx;
	uint16_t			fsmountid;				/* Hosting volume's mount ID */
	uint16_t			refcounter;				/* Object open counter, 0:none, 0x01..0xFF:read mode open count, 0x100:write mode */
	uint8_t				attributes;				/* Object attribute */
	uint8_t				chainstatus;			/* Object chain status (b1-0: =0:not contiguous, =2:contiguous, =3:fragmented in this session, b2:sub-directory stretched) */
	uint8_t				statusflags;			/* File status flags */
	uint8_t				access;					/* The strictest share mode prevailing at the moment ( A_EXCLUSIVE = 0, A_READ = 1, A_APPEND = 2, A_WRITE = 3 )*/
	uint8_t				acccntrs[2];			/* Counters for how many 'open' requested A_READ (index 0) and A_APPEND (index 1). */
	uint8_t				mode;					/* The broadest open mode prevailing at the moment ( M_READ = 1, M_APPEND = 2, M_WRITE = 2 )*/
	uint8_t				modecntrs[2];			/* Counters for how many 'open' requested M_APPEND (index 0) and M_WRITE (index 1). */
	uint8_t				buf[FFS_CONFIG_MAXIMUMSECTORSIZE];	/* File private data read/write window */
	} ffs_object_t;
/*============================================================================*/
#define FFS_CHAINSTATUS_VALID				0
#define FFS_CHAINSTATUS_ALLOCATIONPOSSIBLE	1
#define FFS_CHAINSTATUS_NOFATCHAIN			2
#define FFS_CHAINSTATUS_NEEDSUPDATE			4
/*============================================================================*/
/* File object structure (ffs_file_t) */
/*============================================================================*/
typedef struct
	{
	filesize_t			fptr;			/* File read/write pointer (Zeroed on file open) */
	ffs_object_t		*obj;			/* Object identifier (must be the 1st member to detect invalid object pointer) */
	uint32_t			currentcluster;	/* Current cluster of fpter (invalid when fptr is 0) */
	unsigned int		objectidx;		/* File lock ID origin from 0 (index of file semaphore table Files[]) */
	int					nextidx;
	int					mode;
	int					salt;
	uint8_t				err;			/* Abort flag (error code) */
	} ffs_file_t;
/*============================================================================*/
/* Directory object structure (ffs_directory_t) */
/*============================================================================*/
typedef struct
	{
	lba_t				sect;				/* Current sector (0:Read operation has terminated) */
	ffs_object_t		*obj;				/* Object identifier */
	uint8_t				*dir;				/* Pointer to the directory item in the win[] */
	const ffs_char_t	*pat;				/* Pointer to the name matching pattern */
	uint32_t			direntryoffset;		/* Current read/write offset */
	uint32_t			clust;				/* Current cluster */
	uint32_t			blk_ofs;			/* Offset of current entry block being processed (0xFFFFFFFF:Invalid) */
	unsigned int		objectidx;			/* File lock ID origin from 1 (index of file semaphore table Files[]) */
	uint8_t				fn[FFS_SFNBUFSIZE];	/* SFN (in/out) {body[8],ext[3],status[1]} */
	} ffs_directory_t;
/*============================================================================*/
/*static*/ ffs_volume_t	FFS_Volumes[FFS_CONFIG_MAXVOLUMES];	/* Pointer to the filesystem objects (logical drives) */
static uint16_t		FFS_FsId				=  0;			/* Filesystem mount ID */
static ffs_sync_t	FFS_SystemMutex			= NULL;
/*============================================================================*/
#define	INVALID_VOLUME_INDEX	(-1)
/*============================================================================*/
static int			FFS_FreeVolumesIdx		= INVALID_VOLUME_INDEX;	/* Current drive set by chdrive() */
static int			FFS_CurrentVolumeIdx	= INVALID_VOLUME_INDEX;	/* Current drive set by chdrive() */
/*============================================================================*/
static ffs_object_t	FFS_Objects[FFS_CONFIG_MAXOBJECTS];	/* Open object lock semaphores */
/*============================================================================*/
#define	INVALID_OBJECT_INDEX	(-1)
/*============================================================================*/
static int			FFS_FreeObjectsIdx		= INVALID_OBJECT_INDEX;
/*============================================================================*/
static ffs_file_t	FFS_Files[FFS_CONFIG_MAXIMUMOPENFILES];
static int			FFS_CurrentSalt			= 0;
/*============================================================================*/
#define	INVALID_FILE_INDEX		(-1)
/*============================================================================*/
static int			FFS_FreeFilesIdx		= INVALID_FILE_INDEX;
/*============================================================================*/
static inline int __attribute__((always_inline)) VolumeIndexIsValid( int volidx )
	{
	return (unsigned)volidx < sizeof FFS_Volumes / sizeof FFS_Volumes[0];
	}
/*============================================================================*/
static inline int __attribute__((always_inline)) ThereAreFreeVolumes( void )
	{
	return VolumeIndexIsValid( FFS_FreeVolumesIdx );
	}
/*============================================================================*/
static int GetFreeVolumeIdx( void )
	{
	unsigned	volumeidx;

	/* There are no free volumes available... */
	if( !VolumeIndexIsValid( FFS_FreeVolumesIdx ))
		return INVALID_VOLUME_INDEX;

	/* Remove the first element of the free volumes list from it.  */
	volumeidx						= FFS_FreeVolumesIdx;
	FFS_FreeVolumesIdx				= FFS_Volumes[FFS_FreeVolumesIdx].nextidx;
	memset( &FFS_Volumes[volumeidx], 0, sizeof FFS_Volumes[volumeidx] );
	FFS_Volumes[volumeidx].nextidx	= INVALID_VOLUME_INDEX;

	return (int)volumeidx;
	}
/*============================================================================*/
static int ReturnFreeVolumeIdx( unsigned volumeidx )
	{
	/* volumeidx is invalid... */
	if( !VolumeIndexIsValid( volumeidx ))
		return INVALID_VOLUME_INDEX;

	memset( &FFS_Volumes[volumeidx], 0, sizeof FFS_Volumes[volumeidx] );
	FFS_Volumes[volumeidx].nextidx	= FFS_FreeVolumesIdx;
	FFS_FreeVolumesIdx				= volumeidx;

	return (int)volumeidx;
	}
/*============================================================================*/
static inline int __attribute__((always_inline)) FileIndexIsValid( int fileidx )
	{
	return (unsigned)fileidx < sizeof FFS_Files / sizeof FFS_Files[0];
	}
/*============================================================================*/
static inline int __attribute__((always_inline)) ThereAreFreeFiles( void )
	{
	return FileIndexIsValid( FFS_FreeFilesIdx );
	}
/*============================================================================*/
static int GetFreeFileIdx( void )
	{
	int	fileidx;

	/* There are no free files available... */
	if( !FileIndexIsValid( FFS_FreeFilesIdx ))
		return INVALID_FILE_INDEX;

	/* Remove the first element of the free files list from it.  */
	fileidx						= FFS_FreeFilesIdx;
	FFS_FreeFilesIdx			= FFS_Files[FFS_FreeFilesIdx].nextidx;
	memset( &FFS_Files[fileidx], 0, sizeof FFS_Files[fileidx] );
	FFS_Files[fileidx].nextidx	= INVALID_FILE_INDEX;

	return fileidx;
	}
/*============================================================================*/
static int ReturnFreeFileIdx( int fileidx )
	{
	/* fileidx is invalid... */
	if( !FileIndexIsValid( fileidx ))
		return INVALID_FILE_INDEX;

	memset( &FFS_Files[fileidx], 0, sizeof FFS_Files[fileidx] );
	FFS_Files[fileidx].nextidx	= FFS_FreeFilesIdx;
	FFS_FreeFilesIdx			= fileidx;

	return fileidx;
	}
/*============================================================================*/
static inline int __attribute__((always_inline)) ObjectIndexIsValid( int objectidx )
	{
	return (unsigned)objectidx < sizeof FFS_Objects / sizeof FFS_Objects[0];
	}
/*============================================================================*/
static inline int __attribute__((always_inline)) ThereAreFreeObjects( void )
	{
	return ObjectIndexIsValid( FFS_FreeObjectsIdx );
	}
/*============================================================================*/
static int GetFreeObjectIdx( void )
	{
	int	objectidx;

	/* There are no free objects available... */
	if( !ObjectIndexIsValid( FFS_FreeObjectsIdx ))
		return INVALID_OBJECT_INDEX;

	/* Remove the first element of the free objects list from it.  */
	objectidx						= FFS_FreeObjectsIdx;
	FFS_FreeObjectsIdx				= FFS_Objects[FFS_FreeObjectsIdx].nextidx;
	memset( &FFS_Objects[objectidx], 0, sizeof FFS_Objects[objectidx] );
	FFS_Objects[objectidx].nextidx	= INVALID_OBJECT_INDEX;

	return objectidx;
	}
/*============================================================================*/
static int ReturnFreeObjectIdx( int objectidx )
	{
	/* 'objectidx' is invalid... */
	if( !ObjectIndexIsValid( objectidx ))
		return INVALID_OBJECT_INDEX;

	memset( &FFS_Objects[objectidx], 0, sizeof FFS_Objects[objectidx] );
	FFS_Objects[objectidx].nextidx	= FFS_FreeObjectsIdx;
	FFS_FreeObjectsIdx				= objectidx;

	return objectidx;
	}
/*============================================================================*/
/*-----------------------------------------------------------------------------
    Module Private Functions
------------------------------------------------------------------------------*/
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Load/Store multi-byte word in the FAT structure                            */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static uint16_t ld_word( const uint8_t* ptr )	/*	 Load a 2-byte little-endian word */
	{
	uint16_t rv;

	rv = ptr[1];
	rv = rv << 8 | ptr[0];
	return rv;
	}
/*============================================================================*/
static uint32_t ld_dword( const uint8_t* ptr )	/* Load a 4-byte little-endian word */
	{
	uint32_t rv;

	rv = ptr[3];
	rv = rv << 8 | ptr[2];
	rv = rv << 8 | ptr[1];
	rv = rv << 8 | ptr[0];
	return rv;
	}
/*============================================================================*/
static uint64_t ld_qword( const uint8_t* ptr )	/* Load an 8-byte little-endian word */
	{
	uint64_t rv;

	rv = ptr[7];
	rv = rv << 8 | ptr[6];
	rv = rv << 8 | ptr[5];
	rv = rv << 8 | ptr[4];
	rv = rv << 8 | ptr[3];
	rv = rv << 8 | ptr[2];
	rv = rv << 8 | ptr[1];
	rv = rv << 8 | ptr[0];
	return rv;
	}
/*============================================================================*/
static void st_word( uint8_t* ptr, uint16_t val )	/* Store a 2-byte word in little-endian */
	{
	*ptr++ = (uint8_t)val;
	val >>= 8;
	*ptr++ = (uint8_t)val;
	}
/*============================================================================*/
static void st_dword( uint8_t* ptr, uint32_t val )	/* Store a 4-byte word in little-endian */
	{
	*ptr++ = (uint8_t)val;
	val >>= 8;
	*ptr++ = (uint8_t)val;
	val >>= 8;
	*ptr++ = (uint8_t)val;
	val >>= 8;
	*ptr++ = (uint8_t)val;
	}
/*============================================================================*/
static void st_qword( uint8_t* ptr, uint64_t val )	/* Store an 8-byte word in little-endian */
	{
	*ptr++ = (uint8_t)val;
	val >>= 8;
	*ptr++ = (uint8_t)val;
	val >>= 8;
	*ptr++ = (uint8_t)val;
	val >>= 8;
	*ptr++ = (uint8_t)val;
	val >>= 8;
	*ptr++ = (uint8_t)val;
	val >>= 8;
	*ptr++ = (uint8_t)val;
	val >>= 8;
	*ptr++ = (uint8_t)val;
	val >>= 8;
	*ptr++ = (uint8_t)val;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* String functions                                                      */
/*----------------------------------------------------------------------------*/

/* Test if the byte is DBC 1st byte */
static int dbc_1st( uint8_t c )
	{
	if( FFS_DbcTbl && c >= FFS_DbcTbl[0] )
		{
		if( c <= FFS_DbcTbl[1] )
			return 1;					/* 1st byte range 1 */
		if( c >= FFS_DbcTbl[2] && c <= FFS_DbcTbl[3] )
			return 1;	/* 1st byte range 2 */
		}
	return 0;
	}
/*============================================================================*/
/* Test if the byte is DBC 2nd byte */
static int dbc_2nd( uint8_t c )
	{
	if( FFS_DbcTbl && c >= FFS_DbcTbl[4] )
		{
		if( c <= FFS_DbcTbl[5] )
			return 1;					/* 2nd byte range 1 */
		if( c >= FFS_DbcTbl[6] && c <= FFS_DbcTbl[7] )
			return 1;	/* 2nd byte range 2 */
		if( c >= FFS_DbcTbl[8] && c <= FFS_DbcTbl[9] )
			return 1;	/* 2nd byte range 3 */
		}
	return 0;
	}
/*============================================================================*/
/* Get a Unicode code point from the ffs_char_t string in defined API encodeing */
static uint32_t tchar_t2uni( /* Returns a character in UTF-16 encoding (>=0x10000 on surrogate pair, 0xFFFFFFFF on decode error) */
	const ffs_char_t** str		/* Pointer to pointer to ffs_char_t string in configured encoding */
)
	{
	uint32_t uc;
	const ffs_char_t *p = *str;

#if FFS_CONFIG_LFNUNICODE == 1		/* UTF-16 input */
	ffs_wchar_t wc;

	uc = *p++;	/* Get a unit */
	if( IsSurrogate( uc ))  	/* Surrogate? */
		{
		wc = *p++;		/* Get low surrogate */
		if( !IsSurrogateH( uc ) || !IsSurrogateL( wc ))
			return 0xFFFFFFFF;	/* Wrong surrogate? */
		uc = uc << 16 | wc;
		}

#elif FFS_CONFIG_LFNUNICODE == 2	/* UTF-8 input */
	uint8_t b;
	int nf;

	uc = (uint8_t) * p++;	/* Get an encoding unit */
	if( uc & 0x80 )  	/* Multiple byte code? */
		{
		if(( uc & 0xE0 ) == 0xC0 )  	/* 2-byte sequence? */
			{
			uc &= 0x1F;
			nf = 1;
			}
		else if(( uc & 0xF0 ) == 0xE0 )  	/* 3-byte sequence? */
			{
			uc &= 0x0F;
			nf = 2;
			}
		else if(( uc & 0xF8 ) == 0xF0 )  	/* 4-byte sequence? */
			{
			uc &= 0x07;
			nf = 3;
			}
		else  							/* Wrong sequence */
			return 0xFFFFFFFF;
		do  	/* Get trailing bytes */
			{
			b = (uint8_t) * p++;
			if(( b & 0xC0 ) != 0x80 )
				return 0xFFFFFFFF;	/* Wrong sequence? */
			uc = uc << 6 | ( b & 0x3F );
			}
		while( --nf != 0 );
		if( uc < 0x80 || IsSurrogate( uc ) || uc >= 0x110000 )
			return 0xFFFFFFFF;	/* Wrong code? */
		if( uc >= 0x010000 )
			uc = 0xD800DC00 | (( uc - 0x10000 ) << 6 & 0x3FF0000 ) | ( uc & 0x3FF );	/* Make a surrogate pair if needed */
		}

#elif FFS_CONFIG_LFNUNICODE == 3	/* UTF-32 input */
	uc = (ffs_char_t) * p++;	/* Get a unit */
	if( uc >= 0x110000 || IsSurrogate( uc ))
		return 0xFFFFFFFF;	/* Wrong code? */
	if( uc >= 0x010000 )
		uc = 0xD800DC00 | (( uc - 0x10000 ) << 6 & 0x3FF0000 ) | ( uc & 0x3FF );	/* Make a surrogate pair if needed */

#else		/* ANSI/OEM input */
	ffs_wchar_t wc;

	wc = (uint8_t) * p++;			/* Get a byte */
	if( dbc_1st((uint8_t)wc ))  	/* Is it a DBC 1st byte? */
		{
		uint8_t	b;

		b = (uint8_t) * p++;			/* Get 2nd byte */
		if( !dbc_2nd( b ))
			return 0xFFFFFFFF;	/* Invalid code? */
		wc = ( wc << 8 ) + b;		/* Make a DBC */
		}
	if( wc != 0 )
		{
		wc = ffs_oem2uni( wc, FFS_CodePage );	/* ANSI/OEM ==> Unicode */
		if( wc == 0 )
			return 0xFFFFFFFF;	/* Invalid code? */
		}
	uc = wc;

#endif
	*str = p;	/* Next read pointer */
	return uc;
	}
/*============================================================================*/
/* Store a Unicode char in defined API encoding */
static unsigned int put_utf( /* Returns number of encoding units written (0:buffer overflow or wrong encoding) */
	uint32_t chr,	/* UTF-16 encoded character (Surrogate pair if >=0x10000) */
	ffs_char_t* buf,	/* Output buffer */
	unsigned int szb	/* Size of the buffer */
)
	{
#if FFS_CONFIG_LFNUNICODE == 1	/* UTF-16 output */
	ffs_wchar_t hs, wc;

	hs = (ffs_wchar_t)( chr >> 16 );
	wc = (ffs_wchar_t)chr;
	if( hs == 0 )  	/* Single encoding unit? */
		{
		if( szb < 1 || IsSurrogate( wc ))
			return 0;	/* Buffer overflow or wrong code? */
		*buf = wc;
		return 1;
		}
	if( szb < 2 || !IsSurrogateH( hs ) || !IsSurrogateL( wc ))
		return 0;	/* Buffer overflow or wrong surrogate? */
	*buf++ = hs;
	*buf++ = wc;
	return 2;

#elif FFS_CONFIG_LFNUNICODE == 2	/* UTF-8 output */
	uint32_t hc;

	if( chr < 0x80 )  	/* Single byte code? */
		{
		if( szb < 1 )
			return 0;	/* Buffer overflow? */
		*buf = (ffs_char_t)chr;
		return 1;
		}
	if( chr < 0x800 )  	/* 2-byte sequence? */
		{
		if( szb < 2 )
			return 0;	/* Buffer overflow? */
		*buf++ = (ffs_char_t)( 0xC0 | ( chr >> 6 & 0x1F ));
		*buf++ = (ffs_char_t)( 0x80 | ( chr >> 0 & 0x3F ));
		return 2;
		}
	if( chr < 0x10000 )  	/* 3-byte sequence? */
		{
		if( szb < 3 || IsSurrogate( chr ))
			return 0;	/* Buffer overflow or wrong code? */
		*buf++ = (ffs_char_t)( 0xE0 | ( chr >> 12 & 0x0F ));
		*buf++ = (ffs_char_t)( 0x80 | ( chr >> 6 & 0x3F ));
		*buf++ = (ffs_char_t)( 0x80 | ( chr >> 0 & 0x3F ));
		return 3;
		}
	/* 4-byte sequence */
	if( szb < 4 )
		return 0;	/* Buffer overflow? */
	hc = (( chr & 0xFFFF0000 ) - 0xD8000000 ) >> 6;	/* Get high 10 bits */
	chr = ( chr & 0xFFFF ) - 0xDC00;					/* Get low 10 bits */
	if( hc >= 0x100000 || chr >= 0x400 )
		return 0;	/* Wrong surrogate? */
	chr = ( hc | chr ) + 0x10000;
	*buf++ = (ffs_char_t)( 0xF0 | ( chr >> 18 & 0x07 ));
	*buf++ = (ffs_char_t)( 0x80 | ( chr >> 12 & 0x3F ));
	*buf++ = (ffs_char_t)( 0x80 | ( chr >> 6 & 0x3F ));
	*buf++ = (ffs_char_t)( 0x80 | ( chr >> 0 & 0x3F ));
	return 4;

#elif FFS_CONFIG_LFNUNICODE == 3	/* UTF-32 output */
	uint32_t hc;

	if( szb < 1 )
		return 0;	/* Buffer overflow? */
	if( chr >= 0x10000 )  	/* Out of BMP? */
		{
		hc = (( chr & 0xFFFF0000 ) - 0xD8000000 ) >> 6;	/* Get high 10 bits */
		chr = ( chr & 0xFFFF ) - 0xDC00;					/* Get low 10 bits */
		if( hc >= 0x100000 || chr >= 0x400 )
			return 0;	/* Wrong surrogate? */
		chr = ( hc | chr ) + 0x10000;
		}
	*buf++ = (ffs_char_t)chr;
	return 1;

#else						/* ANSI/OEM output */
	ffs_wchar_t wc;

	wc = ffs_uni2oem( chr, FFS_CodePage );
	if( wc >= 0x100 )  	/* Is this a DBC? */
		{
		if( szb < 2 )
			return 0;
		*buf++ = (char)( wc >> 8 );	/* Store DBC 1st byte */
		*buf++ = (ffs_char_t)wc;			/* Store DBC 2nd byte */
		return 2;
		}
	if( wc == 0 || szb < 1 )
		return 0;	/* Invalid char or buffer overflow? */
	*buf++ = (ffs_char_t)wc;					/* Store the character */
	return 1;
#endif
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Request/Release grant to access the volume                            */
/*----------------------------------------------------------------------------*/
/*----------------------------------------------------------------------------*/
/* File shareing control functions                                       */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static int	SearchObject( ffs_directory_t *dj )
	{
	int	objectidx;

	for( objectidx = 0; ObjectIndexIsValid( objectidx ); objectidx ++ )
		if( FFS_Objects[objectidx].fs != NULL &&
			FFS_Objects[objectidx].fs == dj->obj->fs &&
			FFS_Objects[objectidx].parentdirstartcluster == dj->obj->startcluster &&
			FFS_Objects[objectidx].offsetinparentdir == dj->direntryoffset )

			return objectidx;

	return -1;
	}
/*============================================================================*/
/* File function return code (ffs_fresult_t) */
/*============================================================================*/
typedef enum {
	FR_OK				= 0,		/* (0) Succeeded */
	FR_DISK_ERR,					/* (1) A hard error occurred in the low level disk I/O layer */
	FR_INT_ERR,						/* (2) Assertion failed */
	FR_NOT_READY,					/* (3) The physical drive cannot work */
	FR_NO_FILE,						/* (4) Could not find the file */
	FR_NO_PATH,						/* (5) Could not find the path */
	FR_INVALID_NAME,				/* (6) The path name format is invalid */
	FR_DENIED,						/* (7) Access denied due to prohibited access or directory full */
	FR_EXIST,						/* (8) Access denied due to prohibited access */
	FR_INVALID_OBJECT,				/* (9) The file/directory object is invalid */
	FR_WRITE_PROTECTED,				/* (10) The physical drive is write protected */
	FR_INVALID_DRIVE,				/* (11) The logical drive number is invalid */
	FR_NOT_ENABLED,					/* (12) The volume has no work area */
	FR_NO_FILESYSTEM,				/* (13) There is no valid FAT volume */
	FR_MKFS_ABORTED,				/* (14) The mkfs() aborted due to any problem */
	FR_TIMEOUT,						/* (15) Could not get a grant to access the volume within defined period */
	FR_LOCKED,						/* (16) The operation is rejected according to the file sharing policy */
	FR_NOT_ENOUGH_CORE,				/* (17) LFN working buffer could not be allocated */
	FR_TOO_MANY_OPEN_FILES,			/* (18) Number of open files > FFS_CONFIG_MAXOBJECTS */
	FR_INVALID_PARAMETER			/* (19) Given parameter is invalid */
	} ffs_fresult_t;
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Move/Flush disk access window in the filesystem object                */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static ffs_fresult_t sync_window( ffs_volume_t *fs ) /* Returns FR_OK or FR_DISK_ERR */ /* Filesystem object */
	{
	/* The disk access window is not dirty... */
	if( !fs->windowisdirty )
		/* ...no action needed. */
		return FR_OK;

	/* Write the sector back into the volume... */
	if( disk_write( fs->pdrv, fs->win, fs->sectorinwin, 1 ) != RES_OK )
		/* ...but some error happened. */
		return FR_DISK_ERR;

	/* Clear the window dirty flag */
	fs->windowisdirty = 0;

	/* The sector is in the 1st FAT and we have two FAT copies... */
	if( fs->sectorinwin - fs->fatbase < fs->sectorsperfat && fs->fatcopies == 2 )
		/* ...reflect it to the 2nd FAT... */
		if( disk_write( fs->pdrv, fs->win, fs->sectorinwin + fs->sectorsperfat, 1 ) != RES_OK )
			/* ...but some error happened. */
			return FR_DISK_ERR;

	return FR_OK;
	}
/*============================================================================*/
static ffs_fresult_t move_window( ffs_volume_t *fs, lba_t sect )	/* Returns FR_OK or FR_DISK_ERR */ /* Filesystem object */ /* Sector LBA to make appearance in the fs->win[] */
	{
	if( sect == fs->sectorinwin )  	/* Window offset changed? */
		return FR_OK;

	if( sync_window( fs ) != FR_OK )		/* Flush the window */
		return FR_DISK_ERR;

	if( disk_read( fs->pdrv, fs->win, sect, 1 ) != RES_OK )
		{
		fs->sectorinwin	= (lba_t)-1;	/* Invalidate window if read data is not valid */
		return FR_DISK_ERR;
		}

	fs->sectorinwin = sect;

	return FR_OK;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Synchronize filesystem and data on the storage                        */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static ffs_fresult_t sync_fs( ffs_volume_t *fs )	/* Returns FR_OK or FR_DISK_ERR */ /* Filesystem object */
	{
	if( sync_window( fs ) != FR_OK )
		return FR_DISK_ERR;

	if( fs->fs_type == FFS_FILESYSTEMTYPE_FAT32 && fs->fsi_flag == 1 )  	/* FAT32: Update FSInfo sector if needed */
		{
		/* Create FSInfo structure */
		memset( fs->win, 0, sizeof fs->win );
		st_word( fs->win + BS_55AA, 0xAA55 );					/* Boot signature */
		st_dword( fs->win + FSI_LeadSig, 0x41615252 );			/* Leading signature */
		st_dword( fs->win + FSI_StrucSig, 0x61417272 );			/* Structure signature */
		st_dword( fs->win + FSI_Free_Count, fs->freeclusterscount );	/* Number of free clusters */
		st_dword( fs->win + FSI_Nxt_Free, fs->last_clst );		/* Last allocated cluster */
		fs->sectorinwin	= fs->volbase + 1;							/* Write it into the FSInfo sector (Next to VBR) */
		if( disk_write( fs->pdrv, fs->win, fs->sectorinwin, 1 ) != RES_OK )
			return FR_DISK_ERR;
		fs->fsi_flag = 0;
		}

	/* Make sure that no pending write process in the lower layer */
	if( disk_ioctl( fs->pdrv, CTRL_SYNC, 0 ) != RES_OK )
		return FR_DISK_ERR;

	return FR_OK;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Get physical sector number from cluster number                        */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
#define	FIRST_VALID_CLUSTER	2
/*============================================================================*/
static lba_t clst2sect( /* !=0:Sector number, 0:Failed (invalid cluster#) */
	ffs_volume_t* fs,		/* Filesystem object */
	uint32_t clst		/* Cluster# to be converted */
)
	{
	clst -= FIRST_VALID_CLUSTER;		/* Cluster number is origin from 2 */
	if( clst >= fs->fatentriescount - FIRST_VALID_CLUSTER )
		return 0;		/* Is it invalid cluster number? */
	return fs->database + (lba_t)fs->sectorspercluster * clst;	/* Start sector number of the cluster */
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* FAT access - Read value of an FAT entry                               */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static uint32_t get_fat( /* 0xFFFFFFFF:Disk error, 1:Internal error, 2..0x7FFFFFFF:Cluster status */
	ffs_object_t	*obj,	/* Corresponding object */
	uint32_t		clst		/* Cluster number to get the value */
)
	{
	uint32_t		val;
	ffs_volume_t	*fs = obj->fs;

	if( clst < FIRST_VALID_CLUSTER || clst >= fs->fatentriescount )  	/* Check if in valid range */
		{
		val = 1;	/* Internal error */

		}
	else
		{
		val = 0xFFFFFFFF;	/* Default value falls on disk error */

		switch( fs->fs_type )
			{
			case FFS_FILESYSTEMTYPE_FAT12 :
				{
				unsigned int	wc, bc;

				bc = (unsigned int)clst;
				bc += bc / 2;
				if( move_window( fs, fs->fatbase + ( bc / fs->bytespersector )) != FR_OK )
					break;
				wc = fs->win[bc++ % fs->bytespersector];		/* Get 1st byte of the entry */
				if( move_window( fs, fs->fatbase + ( bc / fs->bytespersector )) != FR_OK )
					break;
				wc |= fs->win[bc % fs->bytespersector] << 8;	/* Merge 2nd byte of the entry */
				val = ( clst & 1 ) ? ( wc >> 4 ) : ( wc & 0xFFF );	/* Adjust bit position */
				break;
				}

			case FFS_FILESYSTEMTYPE_FAT16 :
				if( move_window( fs, fs->fatbase + ( clst / ( fs->bytespersector / 2 ))) != FR_OK )
					break;
				val = ld_word( fs->win + clst * 2 % fs->bytespersector );		/* Simple uint16_t array */
				break;

			case FFS_FILESYSTEMTYPE_FAT32 :
				if( move_window( fs, fs->fatbase + ( clst / ( fs->bytespersector / 4 ))) != FR_OK )
					break;
				val = ld_dword( fs->win + clst * 4 % fs->bytespersector ) & 0x0FFFFFFF;	/* Simple uint32_t array but mask out upper 4 bits */
				break;
			case FFS_FILESYSTEMTYPE_EXFAT :
				if(( obj->sizebytes != 0 && obj->startcluster != 0 ) || obj->chainstatus == FFS_CHAINSTATUS_VALID )  	/* Object except root dir must have valid data length */
					{
					uint32_t cofs = clst - obj->startcluster;	/* Offset from start cluster */
					uint32_t clen = (uint32_t)((lba_t)(( obj->sizebytes - 1 ) / fs->bytespersector ) / fs->sectorspercluster );	/* Number of clusters - 1 */

					if( obj->chainstatus == FFS_CHAINSTATUS_NOFATCHAIN && cofs <= clen )  	/* Is it a contiguous chain? */
						{
						val = ( cofs == clen ) ? 0x7FFFFFFF : clst + 1;	/* No data on the FAT, generate the value */
						break;
						}
					if( obj->chainstatus == ( FFS_CHAINSTATUS_ALLOCATIONPOSSIBLE | FFS_CHAINSTATUS_NOFATCHAIN ) && cofs < obj->sizeoffirstfrag )  	/* Is it in the 1st fragment? */
						{
						val = clst + 1; 	/* Generate the value */
						break;
						}
					if( obj->chainstatus != FFS_CHAINSTATUS_NOFATCHAIN )  	/* Get value from FAT if FAT chain is valid */
						{
						if( obj->sizeoflastfrag != 0 )  	/* Is it on the growing edge? */
							{
							val = 0x7FFFFFFF;	/* Generate EOC */
							}
						else
							{
							if( move_window( fs, fs->fatbase + ( clst / ( fs->bytespersector / 4 ))) != FR_OK )
								break;
							val = ld_dword( fs->win + clst * 4 % fs->bytespersector ) & 0x7FFFFFFF;
							}
						break;
						}
					}
				val = 1;	/* Internal error */
				break;
			default:
				val = 1;	/* Internal error */
			}
		}

	return val;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* FAT access - Change value of an FAT entry                             */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static ffs_fresult_t put_fat( /* FR_OK(0):succeeded, !=0:error */
	ffs_volume_t	*fs,		/* Corresponding filesystem object */
	uint32_t		clst,		/* FAT index number (cluster number) to be changed */
	uint32_t		val		/* New value to be set to the entry */
)
	{
	ffs_fresult_t	res;

	if( clst < FIRST_VALID_CLUSTER || clst >= fs->fatentriescount )  	/* Check if in valid range */
		return FR_INT_ERR;

	switch( fs->fs_type )
		{
		case FFS_FILESYSTEMTYPE_FAT12:
			{
			unsigned int	bc;
			uint8_t			*p;

			bc = (unsigned int)clst;
			bc += bc / 2;	/* bc: byte offset of the entry */
			if(( res = move_window( fs, fs->fatbase + ( bc / fs->bytespersector ))) != FR_OK )
				return res;
			p = fs->win + bc++ % fs->bytespersector;
			*p = ( clst & 1 ) ? (( *p & 0x0F ) | ((uint8_t)val << 4 )) : (uint8_t)val;	/* Update 1st byte */
			fs->windowisdirty = 1;
			if(( res = move_window( fs, fs->fatbase + ( bc / fs->bytespersector ))) != FR_OK )
				return res;
			p = fs->win + bc % fs->bytespersector;
			*p = ( clst & 1 ) ? (uint8_t)( val >> 4 ) : (( *p & 0xF0 ) | ((uint8_t)( val >> 8 ) & 0x0F ));	/* Update 2nd byte */
			fs->windowisdirty = 1;
			return FR_OK;
			}

		case FFS_FILESYSTEMTYPE_FAT16:
			if(( res = move_window( fs, fs->fatbase + ( clst / ( fs->bytespersector / 2 )))) != FR_OK )
				return res;
			st_word( fs->win + clst * 2 % fs->bytespersector, (uint16_t)val );	/* Simple uint16_t array */
			fs->windowisdirty = 1;
			return FR_OK;

		case FFS_FILESYSTEMTYPE_FAT32:
		case FFS_FILESYSTEMTYPE_EXFAT:
			if(( res = move_window( fs, fs->fatbase + ( clst / ( fs->bytespersector / 4 )))) != FR_OK )
				return res;
			if( fs->fs_type != FFS_FILESYSTEMTYPE_EXFAT )
				val = ( val & 0x0FFFFFFF ) | ( ld_dword( fs->win + clst * 4 % fs->bytespersector ) & 0xF0000000 );
			st_dword( fs->win + clst * 4 % fs->bytespersector, val );
			fs->windowisdirty = 1;
			return FR_OK;
		}

	return FR_INT_ERR;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* exFAT: Accessing FAT and Allocation Bitmap                            */
/*----------------------------------------------------------------------------*/
/*============================================================================*/

/*============================================================================*/
/*--------------------------------------*/
/* Find a contiguous free cluster block */
/*--------------------------------------*/
/*============================================================================*/
static uint32_t find_bitmap( /* 0:Not found, 2..:Cluster block found, 0xFFFFFFFF:Disk error */
	ffs_volume_t	*fs,	/* Filesystem object */
	uint32_t		clst,	/* Cluster number to scan from */
	uint32_t		ncl	/* Number of contiguous clusters to find (1..) */
)
	{
	uint8_t bm, bv;
	uint32_t val, scl, refcounter;

	clst -= FIRST_VALID_CLUSTER;	/* The first bit in the bitmap corresponds to cluster #2 */
	if( clst >= fs->fatentriescount - FIRST_VALID_CLUSTER )
		clst = 0;
	scl = val = clst;
	refcounter = 0;
	while( 1 )
		{
		unsigned int	i;

		if( move_window( fs, fs->bitbase + val / 8 / fs->bytespersector ) != FR_OK )
			return 0xFFFFFFFF;
		i = val / 8 % fs->bytespersector;
		bm = 1 << ( val % 8 );
		do
			{
			do
				{
				bv = fs->win[i] & bm;
				bm <<= 1;		/* Get bit value */
				if( ++val >= fs->fatentriescount - FIRST_VALID_CLUSTER )  	/* Next cluster (with wrap-around) */
					{
					val = 0;
					bm = 0;
					i = fs->bytespersector;
					}
				if( bv == 0 )  	/* Is it a free cluster? */
					{
					if( ++refcounter == ncl )
						return scl + FIRST_VALID_CLUSTER;	/* Check if run length is sufficient for required */
					}
				else
					{
					scl = val;
					refcounter = 0;		/* Encountered a cluster in-use, restart to scan */
					}
				if( val == clst )
					return 0;	/* All cluster scanned? */
				}
			while( bm != 0 );
			bm = 1;
			}
		while( ++i < fs->bytespersector );
		}
	}
/*============================================================================*/
/*----------------------------------------*/
/* Set/Clear a block of allocation bitmap */
/*----------------------------------------*/
/*============================================================================*/
static ffs_fresult_t change_bitmap(
	ffs_volume_t	*fs,	/* Filesystem object */
	uint32_t		clst,	/* Cluster number to change from */
	uint32_t		ncl,	/* Number of clusters to be changed */
	int				bv		/* bit value to be set (0 or 1) */
)
	{
	uint8_t bm;
	unsigned int i;
	lba_t sect;


	clst -= FIRST_VALID_CLUSTER;	/* The first bit corresponds to cluster #2 */
	sect = fs->bitbase + clst / 8 / fs->bytespersector;	/* Sector address */
	i = clst / 8 % fs->bytespersector;					/* Byte offset in the sector */
	bm = 1 << ( clst % 8 );					/* Bit mask in the byte */
	while( 1 )
		{
		if( move_window( fs, sect++ ) != FR_OK )
			return FR_DISK_ERR;
		do
			{
			do
				{
				if( bv == (int)(( fs->win[i] & bm ) != 0 ))
					return FR_INT_ERR;	/* Is the bit expected value? */
				fs->win[i] ^= bm;	/* Flip the bit */
				fs->windowisdirty = 1;
				if( --ncl == 0 )
					return FR_OK;	/* All bits processed? */
				}
			while( bm <<= 1 );		/* Next bit */
			bm = 1;
			}
		while( ++i < fs->bytespersector );		/* Next byte */
		i = 0;
		}
	}
/*============================================================================*/
/*---------------------------------------------*/
/* Fill the first fragment of the FAT chain    */
/*---------------------------------------------*/
/*============================================================================*/
static ffs_fresult_t fill_first_frag(
	ffs_object_t* obj	/* Pointer to the corresponding object */
)
	{

	if( obj->chainstatus == ( FFS_CHAINSTATUS_ALLOCATIONPOSSIBLE | FFS_CHAINSTATUS_NOFATCHAIN ))  	/* Has the object been changed 'fragmented' in this session? */
		{
		uint32_t		cl, n;

		for( cl = obj->startcluster, n = obj->sizeoffirstfrag; n; cl++, n-- )  	/* Create cluster chain on the FAT */
			{
			ffs_fresult_t	res;

			res = put_fat( obj->fs, cl, cl + 1 );
			if( res != FR_OK )
				return res;
			}
		obj->chainstatus = FFS_CHAINSTATUS_VALID;	/* Change status 'FAT chain is valid' */
		}
	return FR_OK;
	}
/*============================================================================*/
/*---------------------------------------------*/
/* Fill the last fragment of the FAT chain     */
/*---------------------------------------------*/
/*============================================================================*/
static ffs_fresult_t fill_last_frag(
	ffs_object_t* obj,	/* Pointer to the corresponding object */
	uint32_t lcl,		/* Last cluster of the fragment */
	uint32_t term		/* Value to set the last FAT entry */
)
	{

	while( obj->sizeoflastfrag > 0 )  	/* Create the chain of last fragment */
		{
		ffs_fresult_t	res;

		res = put_fat( obj->fs, lcl - obj->sizeoflastfrag + 1, ( obj->sizeoflastfrag > 1 ) ? lcl - obj->sizeoflastfrag + 2 : term );	//@@@??? FIRST_VALID_CLUSTER ???
		if( res != FR_OK )
			return res;
		obj->sizeoflastfrag--;
		}
	return FR_OK;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* FAT handling - Remove a cluster chain                                 */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static ffs_fresult_t remove_chain( /* FR_OK(0):succeeded, !=0:error */
	ffs_object_t* obj,		/* Corresponding object */
	uint32_t clst,			/* Cluster to remove a chain from */
	uint32_t pclst			/* Previous cluster of clst (0 if entire chain) */
)
	{
	ffs_fresult_t	res;
	uint32_t		nxt;
	ffs_volume_t	*fs = obj->fs;
	uint32_t		scl = clst, ecl = clst;
	lba_t			rt[2];

	if( clst < FIRST_VALID_CLUSTER || clst >= fs->fatentriescount )
		return FR_INT_ERR;	/* Check if in valid range */

	/* Mark the previous cluster 'EOC' on the FAT if it exists */
	if( pclst != 0 && ( fs->fs_type != FFS_FILESYSTEMTYPE_EXFAT || obj->chainstatus != FFS_CHAINSTATUS_NOFATCHAIN ))
		{
		res = put_fat( fs, pclst, 0xFFFFFFFF );
		if( res != FR_OK )
			return res;
		}

	/* Remove the chain */
	do
		{
		nxt = get_fat( obj, clst );			/* Get cluster status */
		if( nxt == 0 )
			break;				/* Empty cluster? */
		if( nxt == 1 )
			return FR_INT_ERR;	/* Internal error? */
		if( nxt == 0xFFFFFFFF )
			return FR_DISK_ERR;	/* Disk error? */
		if( fs->fs_type != FFS_FILESYSTEMTYPE_EXFAT )
			{
			res = put_fat( fs, clst, 0 );		/* Mark the cluster 'free' on the FAT */
			if( res != FR_OK )
				return res;
			}
		if( fs->freeclusterscount < fs->fatentriescount - FIRST_VALID_CLUSTER )  	/* Update FSINFO */
			{
			fs->freeclusterscount++;
			fs->fsi_flag |= 1;
			}
		if( ecl + 1 == nxt )  	/* Is next cluster contiguous? */
			ecl = nxt;
		else  				/* End of contiguous cluster block */
			{
			if( fs->fs_type == FFS_FILESYSTEMTYPE_EXFAT )
				{
				res = change_bitmap( fs, scl, ecl - scl + 1, 0 );	/* Mark the cluster block 'free' on the bitmap */
				if( res != FR_OK )
					return res;
				}
			rt[0] = clst2sect( fs, scl );					/* Start of data area to be freed */
			rt[1] = clst2sect( fs, ecl ) + fs->sectorspercluster - 1;	/* End of data area to be freed */
			disk_ioctl( fs->pdrv, CTRL_TRIM, rt );		/* Inform storage device that the data in the block may be erased */
			scl = ecl = nxt;
			}
		clst = nxt;					/* Next cluster */
		}
	while( clst < fs->fatentriescount );	/* Repeat while not the last link */

	/* Some post processes for chain status */
	if( fs->fs_type == FFS_FILESYSTEMTYPE_EXFAT )
		{
		if( pclst == 0 )  	/* Has the entire chain been removed? */
			{
			obj->chainstatus = FFS_CHAINSTATUS_VALID;		/* Change the chain status 'initial' */
			}
		else
			{
			if( obj->chainstatus == FFS_CHAINSTATUS_VALID )  	/* Is it a fragmented chain from the beginning of this session? */
				{
				clst = obj->startcluster;		/* Follow the chain to check if it gets contiguous */
				while( clst != pclst )
					{
					nxt = get_fat( obj, clst );
					if( nxt < FIRST_VALID_CLUSTER )
						return FR_INT_ERR;
					if( nxt == 0xFFFFFFFF )
						return FR_DISK_ERR;
					if( nxt != clst + 1 )
						break;	/* Not contiguous? */
					clst++;
					}
				if( clst == pclst )  	/* Has the chain got contiguous again? */
					{
					obj->chainstatus = FFS_CHAINSTATUS_NOFATCHAIN;		/* Change the chain status 'contiguous' */
					}
				}
			else
				{
				if( obj->chainstatus == ( FFS_CHAINSTATUS_ALLOCATIONPOSSIBLE | FFS_CHAINSTATUS_NOFATCHAIN ) && pclst >= obj->startcluster && pclst <= obj->startcluster + obj->sizeoffirstfrag )  	/* Was the chain fragmented in this session and got contiguous again? */
					{
					obj->chainstatus = FFS_CHAINSTATUS_NOFATCHAIN;	/* Change the chain status 'contiguous' */
					}
				}
			}
		}
	return FR_OK;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* FAT handling - Stretch a chain or Create a new chain                  */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static uint32_t create_chain( /* 0:No free cluster, 1:Internal error, 0xFFFFFFFF:Disk error, >=2:New cluster# */
	ffs_object_t* obj,		/* Corresponding object */
	uint32_t clst			/* Cluster# to stretch, 0:Create a new chain */
)
	{
	uint32_t		cs, ncl, scl;
	ffs_fresult_t	res;
	ffs_volume_t	*fs = obj->fs;


	if( clst == 0 )  	/* Create a new chain */
		{
		scl = fs->last_clst;				/* Suggested cluster to start to find */
		if( scl == 0 || scl >= fs->fatentriescount )
			scl = 1;
		}
	else  				/* Stretch a chain */
		{
		cs = get_fat( obj, clst );			/* Check the cluster status */
		if( cs < FIRST_VALID_CLUSTER )
			return 1;				/* Test for insanity */
		if( cs == 0xFFFFFFFF )
			return cs;	/* Test for disk error */
		if( cs < fs->fatentriescount )
			return cs;	/* It is already followed by next cluster */
		scl = clst;							/* Cluster to start to find */
		}

	if( fs->freeclusterscount == 0 )
		return 0;		/* No free cluster */

	if( fs->fs_type == FFS_FILESYSTEMTYPE_EXFAT )  	/* On the exFAT volume */
		{
		ncl = find_bitmap( fs, scl, 1 );				/* Find a free cluster */
		if( ncl == 0 || ncl == 0xFFFFFFFF )
			return ncl;	/* No free cluster or hard error? */
		res = change_bitmap( fs, ncl, 1, 1 );			/* Mark the cluster 'in use' */
		if( res == FR_INT_ERR )
			return 1;
		if( res == FR_DISK_ERR )
			return 0xFFFFFFFF;
		if( clst == 0 )  							/* Is it a new chain? */
			{
			obj->chainstatus = FFS_CHAINSTATUS_NOFATCHAIN;							/* Set status 'contiguous' */
			}
		else  									/* It is a stretched chain */
			{
			if( obj->chainstatus == FFS_CHAINSTATUS_NOFATCHAIN && ncl != scl + 1 )  	/* Is the chain got fragmented? */
				{
				obj->sizeoffirstfrag	= scl - obj->startcluster;	/* Set size of the contiguous part */
				obj->chainstatus		= FFS_CHAINSTATUS_ALLOCATIONPOSSIBLE | FFS_CHAINSTATUS_NOFATCHAIN;						/* Change status 'just fragmented' */
				}
			}
		if( obj->chainstatus != FFS_CHAINSTATUS_NOFATCHAIN )  	/* Is the file non-contiguous? */
			{
			if( ncl == clst + 1 )  	/* Is the cluster next to previous one? */
				{
				obj->sizeoflastfrag = obj->sizeoflastfrag ? obj->sizeoflastfrag + 1 : 2;	/* Increment size of last fragment */
				}
			else  				/* New fragment */
				{
				if( obj->sizeoflastfrag == 0 )
					obj->sizeoflastfrag = 1;
				res = fill_last_frag( obj, clst, ncl );	/* Fill last fragment on the FAT and link it to new one */
				if( res == FR_OK )
					obj->sizeoflastfrag = 1;
				}
			}
		}
	else
		{
		/* On the FAT/FAT32 volume */
		ncl = 0;
		if( scl == clst )  						/* Stretching an existing chain? */
			{
			ncl = scl + 1;						/* Test if next cluster is free */
			if( ncl >= fs->fatentriescount )
				ncl = FIRST_VALID_CLUSTER;
			cs = get_fat( obj, ncl );				/* Get next cluster status */
			if( cs == 1 || cs == 0xFFFFFFFF )
				return cs;	/* Test for error */
			if( cs != 0 )  						/* Not free? */
				{
				cs = fs->last_clst;				/* Start at suggested cluster if it is valid */
				if( cs >= FIRST_VALID_CLUSTER && cs < fs->fatentriescount )
					scl = cs;
				ncl = 0;
				}
			}
		if( ncl == 0 )  	/* The new cluster cannot be contiguous and find another fragment */
			{
			ncl = scl;	/* Start cluster */
			while( 1 )
				{
				ncl++;							/* Next cluster */
				if( ncl >= fs->fatentriescount )  		/* Check wrap-around */
					{
					ncl = FIRST_VALID_CLUSTER;
					if( ncl > scl )
						return 0;	/* No free cluster found? */
					}
				cs = get_fat( obj, ncl );			/* Get the cluster status */
				if( cs == 0 )
					break;				/* Found a free cluster? */
				if( cs == 1 || cs == 0xFFFFFFFF )
					return cs;	/* Test for error */
				if( ncl == scl )
					return 0;		/* No free cluster found? */
				}
			}
		res = put_fat( fs, ncl, 0xFFFFFFFF );		/* Mark the new cluster 'EOC' */
		if( res == FR_OK && clst != 0 )
			{
			res = put_fat( fs, clst, ncl );		/* Link it from the previous one if needed */
			}
		}

	if( res == FR_OK )  			/* Update FSINFO if function succeeded. */
		{
		fs->last_clst = ncl;
		if( fs->freeclusterscount <= fs->fatentriescount - FIRST_VALID_CLUSTER )
			fs->freeclusterscount--;
		fs->fsi_flag |= 1;
		}
	else
		{
		ncl = ( res == FR_DISK_ERR ) ? 0xFFFFFFFF : 1;	/* Failed. Generate error status */
		}

	return ncl;		/* Return new cluster number or error status */
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Directory handling - Fill a cluster with zeros                        */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static ffs_fresult_t dir_clear( /* Returns FR_OK or FR_DISK_ERR */
	ffs_volume_t	*fs,		/* Filesystem object */
	uint32_t		clst		/* Directory table to clear */
)
	{
	lba_t sect;
	unsigned int n, szb;
	uint8_t *ibuf;


	if( sync_window( fs ) != FR_OK )
		return FR_DISK_ERR;	/* Flush disk access window */
	sect = clst2sect( fs, clst );		/* Top of the cluster */
	fs->sectorinwin = sect;				/* Set window to top of the cluster */
	memset( fs->win, 0, sizeof fs->win );	/* Clear window buffer */
	ibuf = fs->win;
	szb = 1;	/* Use window buffer (many single-sector writes may take a time) */
	for( n = 0; n < fs->sectorspercluster && disk_write( fs->pdrv, ibuf, sect + n, szb ) == RES_OK; n += szb )	/* Fill the cluster with 0 */
		{}
	return ( n == fs->sectorspercluster ) ? FR_OK : FR_DISK_ERR;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Directory handling - Set directory index                              */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static ffs_fresult_t dir_sdi( /* FR_OK(0):succeeded, !=0:error */
	ffs_directory_t	*dp,		/* Pointer to directory object */
	uint32_t		ofs		/* Offset of directory table */
)
	{
	uint32_t		clst;
	ffs_volume_t	*fs = dp->obj->fs;


	if( ofs >= (uint32_t)(( fs->fs_type == FFS_FILESYSTEMTYPE_EXFAT ) ? MAX_DIR_EX : MAX_DIR ) || ofs % SIZE_OF_DIR_ENTRY )  	/* Check range of offset and alignment */
		return FR_INT_ERR;
	dp->direntryoffset = ofs;				/* Set current offset */
	clst = dp->obj->startcluster;		/* Table start cluster (0:root) */
	if( clst == 0 && fs->fs_type >= FFS_FILESYSTEMTYPE_FAT32 )  	/* Replace cluster# 0 with root cluster# */
		{
		clst = (uint32_t)fs->dirbase;
		dp->obj->chainstatus = FFS_CHAINSTATUS_VALID;	/* exFAT: Root dir has an FAT chain */
		}

	if( clst == 0 )  	/* Static table (root-directory on the FAT volume) */
		{
		if( ofs / SIZE_OF_DIR_ENTRY >= fs->rootdirentriescount )
			return FR_INT_ERR;	/* Is index out of range? */
		dp->sect = fs->dirbase;

		}
	else  			/* Dynamic table (sub-directory or root-directory on the FAT32/exFAT volume) */
		{
		uint32_t	csz;

		csz = (uint32_t)fs->sectorspercluster * fs->bytespersector;	/* Bytes per cluster */
		while( ofs >= csz )  				/* Follow cluster chain */
			{
			clst = get_fat( dp->obj, clst );				/* Get next cluster */
			if( clst == 0xFFFFFFFF )
				return FR_DISK_ERR;	/* Disk error */
			if( clst < FIRST_VALID_CLUSTER || clst >= fs->fatentriescount )
				return FR_INT_ERR;	/* Reached to end of table or internal error */
			ofs -= csz;
			}
		dp->sect = clst2sect( fs, clst );
		}
	dp->clust = clst;					/* Current cluster# */
	if( dp->sect == 0 )
		return FR_INT_ERR;
	dp->sect += ofs / fs->bytespersector;			/* Sector# of the directory entry */
	dp->dir = fs->win + ( ofs % fs->bytespersector );	/* Pointer to the entry in the win[] */

	return FR_OK;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Directory handling - Move directory table index next                  */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static ffs_fresult_t dir_next( /* FR_OK(0):succeeded, FR_NO_FILE:End of table, FR_DENIED:Could not stretch */
	ffs_directory_t	*dp,				/* Pointer to the directory object */
	int				stretch				/* 0: Do not stretch table, 1: Stretch table if needed */
)
	{
	uint32_t		ofs;
	ffs_volume_t	*fs = dp->obj->fs;


	ofs = dp->direntryoffset + SIZE_OF_DIR_ENTRY;	/* Next entry */
	if( ofs >= (uint32_t)(( fs->fs_type == FFS_FILESYSTEMTYPE_EXFAT ) ? MAX_DIR_EX : MAX_DIR ))
		dp->sect = 0;	/* Disable it if the offset reached the max value */
	if( dp->sect == 0 )
		return FR_NO_FILE;	/* Report EOT if it has been disabled */

	if( ofs % fs->bytespersector == 0 )  	/* Sector changed? */
		{
		dp->sect++;				/* Next sector */

		if( dp->clust == 0 )  	/* Static table */
			{
			if( ofs / SIZE_OF_DIR_ENTRY >= fs->rootdirentriescount )  	/* Report EOT if it reached end of static table */
				{
				dp->sect = 0;
				return FR_NO_FILE;
				}
			}
		else  					/* Dynamic table */
			{
			if(( ofs / fs->bytespersector & ( fs->sectorspercluster - 1 )) == 0 )  	/* Cluster changed? */
				{
				uint32_t	clst;

				clst = get_fat( dp->obj, dp->clust );		/* Get next cluster */
				if( clst < FIRST_VALID_CLUSTER )
					return FR_INT_ERR;			/* Internal error */
				if( clst == 0xFFFFFFFF )
					return FR_DISK_ERR;	/* Disk error */
				if( clst >= fs->fatentriescount )  					/* It reached end of dynamic table */
					{
					if( !stretch )  								/* If no stretch, report EOT */
						{
						dp->sect = 0;
						return FR_NO_FILE;
						}
					clst = create_chain( dp->obj, dp->clust );	/* Allocate a cluster */
					if( clst == 0 )
						return FR_DENIED;			/* No free cluster */
					if( clst == 1 )
						return FR_INT_ERR;			/* Internal error */
					if( clst == 0xFFFFFFFF )
						return FR_DISK_ERR;	/* Disk error */
					if( dir_clear( fs, clst ) != FR_OK )
						return FR_DISK_ERR;	/* Clean up the stretched table */
					dp->obj->chainstatus |= FFS_CHAINSTATUS_NEEDSUPDATE;			/* exFAT: The directory has been stretched */
					}
				dp->clust = clst;		/* Initialize data for new cluster */
				dp->sect = clst2sect( fs, clst );
				}
			}
		}
	dp->direntryoffset = ofs;						/* Current entry */
	dp->dir = fs->win + ofs % fs->bytespersector;	/* Pointer to the entry in the win[] */

	return FR_OK;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Directory handling - Reserve a block of directory entries             */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static ffs_fresult_t dir_alloc( /* FR_OK(0):succeeded, !=0:error */
	ffs_directory_t	*dp,				/* Pointer to the directory object */
	unsigned int	n_ent				/* Number of contiguous entries to allocate */
)
	{
	ffs_fresult_t	res;
	ffs_volume_t	*fs = dp->obj->fs;


	res = dir_sdi( dp, 0 );
	if( res == FR_OK )
		{
		unsigned int	n;

		n = 0;
		do
			{
			res = move_window( fs, dp->sect );
			if( res != FR_OK )
				break;
			if(( fs->fs_type == FFS_FILESYSTEMTYPE_EXFAT ) ? (int)(( dp->dir[XDIR_Type] & 0x80 ) == 0 ) : (int)( dp->dir[DIR_Name] == DDEM || dp->dir[DIR_Name] == 0 ))  	/* Is the entry free? */
				{
				if( ++n == n_ent )
					break;	/* Is a block of contiguous free entries found? */
				}
			else
				{
				n = 0;				/* Not a free entry, restart to search */
				}
			res = dir_next( dp, 1 );	/* Next entry with table stretch enabled */
			}
		while( res == FR_OK );
		}

	if( res == FR_NO_FILE )
		res = FR_DENIED;	/* No directory entry to allocate */
	return res;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* FAT: Directory handling - Load/Store start cluster number             */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static uint32_t ld_clust( /* Returns the top cluster value of the SFN entry */
	const ffs_volume_t	*fs,			/* Pointer to the fs object */
	const uint8_t		*dir		/* Pointer to the key entry */
)
	{
	uint32_t cl;

	cl = ld_word( dir + DIR_FstClusLO );
	if( fs->fs_type == FFS_FILESYSTEMTYPE_FAT32 )
		cl |= (uint32_t)ld_word( dir + DIR_FstClusHI ) << 16;

	return cl;
	}


static void st_clust(
	const ffs_volume_t	*fs,	/* Pointer to the fs object */
	uint8_t				*dir,	/* Pointer to the key entry */
	uint32_t			cl		/* Value to be set */
)
	{
	st_word( dir + DIR_FstClusLO, (uint16_t)cl );
	if( fs->fs_type == FFS_FILESYSTEMTYPE_FAT32 )
		st_word( dir + DIR_FstClusHI, (uint16_t)( cl >> 16 ));
	}
/*============================================================================*/
/*--------------------------------------------------------*/
/* FAT-LFN: Compare a part of file name with an LFN entry */
/*--------------------------------------------------------*/
/*============================================================================*/
static int cmp_lfn( /* 1:matched, 0:not matched */
	const ffs_wchar_t	*lfnbuf,	/* Pointer to the LFN working buffer to be compared */
	const uint8_t		*dir				/* Pointer to the directory entry containing the part of LFN */
)
	{
	unsigned int i, s;
	ffs_wchar_t wc;

	if( ld_word( dir + LDIR_FstClusLO ) != 0 )
		return 0;	/* Check LDIR_FstClusLO */

	i = (( dir[LDIR_Ord] & 0x3F ) - 1 ) * 13;	/* Offset in the LFN buffer */

	for( wc = 1, s = 0; s < 13; s++ )  		/* Process all characters in the entry */
		{
		ffs_wchar_t uc;

		uc = ld_word( dir + LfnOfs[s] );		/* Pick an LFN character */
		if( wc != 0 )
			{
			if( i >= FFS_CONFIG_MAXIMUMLFN + 1 || ffs_wtoupper( uc ) != ffs_wtoupper( lfnbuf[i++] ))  	/* Compare it */
				{
				return 0;					/* Not matched */
				}
			wc = uc;
			}
		else
			{
			if( uc != 0xFFFF )
				return 0;		/* Check filler */
			}
		}

	if(( dir[LDIR_Ord] & LLEF ) && wc && lfnbuf[i] )
		return 0;	/* Last segment matched but different length */

	return 1;		/* The part of LFN matched */
	}
/*============================================================================*/
/*-----------------------------------------------------*/
/* FAT-LFN: Pick a part of file name from an LFN entry */
/*-----------------------------------------------------*/
/*============================================================================*/
static int pick_lfn( /* 1:succeeded, 0:buffer overflow or invalid LFN entry */
	ffs_wchar_t		*lfnbuf,		/* Pointer to the LFN working buffer */
	const uint8_t	*dir			/* Pointer to the LFN entry */
)
	{
	unsigned int i, s;
	ffs_wchar_t wc;

	if( ld_word( dir + LDIR_FstClusLO ) != 0 )
		return 0;	/* Check LDIR_FstClusLO is 0 */

	i = (( dir[LDIR_Ord] & ~LLEF ) - 1 ) * 13;	/* Offset in the LFN buffer */

	for( wc = 1, s = 0; s < 13; s++ )  		/* Process all characters in the entry */
		{
		ffs_wchar_t	uc;

		uc = ld_word( dir + LfnOfs[s] );		/* Pick an LFN character */
		if( wc != 0 )
			{
			if( i >= FFS_CONFIG_MAXIMUMLFN + 1 )
				return 0;	/* Buffer overflow? */
			lfnbuf[i++] = wc = uc;			/* Store it */
			}
		else
			{
			if( uc != 0xFFFF )
				return 0;		/* Check filler */
			}
		}

	if( dir[LDIR_Ord] & LLEF && wc != 0 )  	/* Put terminator if it is the last LFN part and not terminated */
		{
		if( i >= FFS_CONFIG_MAXIMUMLFN + 1 )
			return 0;	/* Buffer overflow? */
		lfnbuf[i] = 0;
		}

	return 1;		/* The part of LFN is valid */
	}
/*============================================================================*/
/*-----------------------------------------*/
/* FAT-LFN: Create an entry of LFN entries */
/*-----------------------------------------*/
/*============================================================================*/
static void put_lfn(
	const ffs_wchar_t* lfn,	/* Pointer to the LFN */
	uint8_t* dir,			/* Pointer to the LFN entry to be created */
	uint8_t ord,			/* LFN order (1-20) */
	uint8_t sum			/* Checksum of the corresponding SFN */
)
	{
	unsigned int i, s;
	ffs_wchar_t wc;


	dir[LDIR_Chksum] = sum;			/* Set checksum */
	dir[LDIR_Attr] = AM_LFN;		/* Set attribute. LFN entry */
	dir[LDIR_Type] = 0;
	st_word( dir + LDIR_FstClusLO, 0 );

	i = ( ord - 1 ) * 13;				/* Get offset in the LFN working buffer */
	s = wc = 0;
	do
		{
		if( wc != 0xFFFF )
			wc = lfn[i++];	/* Get an effective character */
		st_word( dir + LfnOfs[s], wc );		/* Put it */
		if( wc == 0 )
			wc = 0xFFFF;			/* Padding characters for following items */
		}
	while( ++s < 13 );
	if( wc == 0xFFFF || !lfn[i] )
		ord |= LLEF;	/* Last LFN part is the start of LFN sequence */
	dir[LDIR_Ord] = ord;			/* Set the LFN order */
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* FAT-LFN: Create a Numbered SFN                                        */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static void gen_numname(
	uint8_t* dst,			/* Pointer to the buffer to store numbered SFN */
	const uint8_t* src,	/* Pointer to SFN in directory form */
	const ffs_wchar_t* lfn,	/* Pointer to LFN */
	unsigned int seq			/* Sequence number */
)
	{
	unsigned int	i, j;
	uint8_t			ns[8];

	memcpy( dst, src, 11 );	/* Prepare the SFN to be modified */

	if( seq > 5 )  	/* In case of many collisions, generate a hash number instead of sequential number */
		{
		uint32_t	sreg;

		sreg = seq;
		while( *lfn )  	/* Create a CRC as hash value */
			{
			ffs_wchar_t	wc;

			wc = *lfn++;
			for( i = 0; i < 16; i++ )
				{
				sreg = ( sreg << 1 ) + ( wc & 1 );
				wc >>= 1;
				if( sreg & 0x10000 )
					sreg ^= 0x11021;
				}
			}
		seq = (unsigned int)sreg;
		}

	/* Make suffix (~ + hexadecimal) */
	i = 7;
	do
		{
		uint8_t		c;

		c = (uint8_t)(( seq % 16 ) + '0' );
		seq /= 16;
		if( c > '9' )
			c += 7;
		ns[i--] = c;
		}
	while( i && seq );
	ns[i] = '~';

	/* Append the suffix to the SFN body */
	for( j = 0; j < i && dst[j] != ' '; j++ )  	/* Find the offset to append */
		{
		if( dbc_1st( dst[j] ))  	/* To avoid DBC break up */
			{
			if( j == i - 1 )
				break;
			j++;
			}
		}
	do  	/* Append the suffix */
		{
		dst[j++] = ( i < 8 ) ? ns[i++] : ' ';
		}
	while( j < 8 );
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* FAT-LFN: Calculate checksum of an SFN entry                           */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static uint8_t sum_sfn(
	const uint8_t* dir		/* Pointer to the SFN entry */
)
	{
	uint8_t sum = 0;
	unsigned int n = 11;

	do
		{
		sum = ( sum >> 1 ) + ( sum << 7 ) + *dir++;
		}
	while( --n );
	return sum;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* exFAT: Checksum                                                       */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static uint16_t xdir_sum( /* Get checksum of the directoly entry block */
	const uint8_t* dir		/* Directory entry block to be calculated */
)
	{
	unsigned int i, szblk;
	uint16_t sum;


	szblk = ( dir[XDIR_NumSec] + 1 ) * SIZE_OF_DIR_ENTRY;	/* Number of bytes of the entry block */
	for( i = sum = 0; i < szblk; i++ )
		{
		if( i == XDIR_SetSum )  	/* Skip 2-byte sum field */
			i++;
		else
			sum = (( sum & 1 ) ? 0x8000 : 0 ) + ( sum >> 1 ) + dir[i];
		}
	return sum;
	}
/*============================================================================*/
static uint16_t xname_sum( /* Get check sum (to be used as hash) of the file name */
	const ffs_wchar_t* name	/* File name to be calculated */
)
	{
	ffs_wchar_t chr;
	uint16_t sum = 0;


	while(( chr = *name++ ) != 0 )
		{
		chr = (ffs_wchar_t)ffs_wtoupper( chr );		/* File name needs to be up-case converted */
		sum = (( sum & 1 ) ? 0x8000 : 0 ) + ( sum >> 1 ) + ( chr & 0xFF );
		sum = (( sum & 1 ) ? 0x8000 : 0 ) + ( sum >> 1 ) + ( chr >> 8 );
		}
	return sum;
	}
/*============================================================================*/
static uint32_t xsum32( /* Returns 32-bit checksum */
	uint8_t  dat,			/* Byte to be calculated (byte-by-byte processing) */
	uint32_t sum			/* Previous sum value */
)
	{
	sum = (( sum & 1 ) ? 0x80000000 : 0 ) + ( sum >> 1 ) + dat;
	return sum;
	}
/*============================================================================*/
/*------------------------------------*/
/* exFAT: Get a directory entry block */
/*------------------------------------*/
/*============================================================================*/
static ffs_fresult_t load_xdir( /* FR_INT_ERR: invalid entry block */
	ffs_directory_t	*dp					/* Reading directory object pointing top of the entry block to load */
)
	{
	ffs_fresult_t res;
	unsigned int i, sz_ent;
	uint8_t *dirb = dp->obj->fs->dirbuf;	/* Pointer to the on-memory directory entry block 85+C0+C1s */


	/* Load file directory entry */
	res = move_window( dp->obj->fs, dp->sect );
	if( res != FR_OK )
		return res;
	if( dp->dir[XDIR_Type] != ET_FILEDIR )
		return FR_INT_ERR;	/* Invalid order */
	memcpy( dirb + 0 * SIZE_OF_DIR_ENTRY, dp->dir, SIZE_OF_DIR_ENTRY );
	sz_ent = ( dirb[XDIR_NumSec] + 1 ) * SIZE_OF_DIR_ENTRY;
	if( sz_ent < 3 * SIZE_OF_DIR_ENTRY || sz_ent > 19 * SIZE_OF_DIR_ENTRY )
		return FR_INT_ERR;

	/* Load stream extension entry */
	res = dir_next( dp, 0 );
	if( res == FR_NO_FILE )
		res = FR_INT_ERR;	/* It cannot be */
	if( res != FR_OK )
		return res;
	res = move_window( dp->obj->fs, dp->sect );
	if( res != FR_OK )
		return res;
	if( dp->dir[XDIR_Type] != ET_STREAM )
		return FR_INT_ERR;	/* Invalid order */
	memcpy( dirb + 1 * SIZE_OF_DIR_ENTRY, dp->dir, SIZE_OF_DIR_ENTRY );
	if( MAXDIRB( dirb[XDIR_NumName] ) > sz_ent )
		return FR_INT_ERR;

	/* Load file name entries */
	i = 2 * SIZE_OF_DIR_ENTRY;	/* Name offset to load */
	do
		{
		res = dir_next( dp, 0 );
		if( res == FR_NO_FILE )
			res = FR_INT_ERR;	/* It cannot be */
		if( res != FR_OK )
			return res;
		res = move_window( dp->obj->fs, dp->sect );
		if( res != FR_OK )
			return res;
		if( dp->dir[XDIR_Type] != ET_FILENAME )
			return FR_INT_ERR;	/* Invalid order */
		if( i < MAXDIRB( FFS_CONFIG_MAXIMUMLFN ))
			memcpy( dirb + i, dp->dir, SIZE_OF_DIR_ENTRY );
		}
	while(( i += SIZE_OF_DIR_ENTRY ) < sz_ent );

	/* Sanity check (do it for only accessible object) */
	if( i <= MAXDIRB( FFS_CONFIG_MAXIMUMLFN ))
		{
		if( xdir_sum( dirb ) != ld_word( dirb + XDIR_SetSum ))
			return FR_INT_ERR;
		}
	return FR_OK;
	}
/*============================================================================*/
/*------------------------------------------------------------------*/
/* exFAT: Initialize object allocation info with loaded entry block */
/*------------------------------------------------------------------*/
/*============================================================================*/
static void init_alloc_info(
	ffs_volume_t* fs,		/* Filesystem object */
	ffs_object_t* obj	/* Object allocation information to be initialized */
)
	{
	obj->startcluster	= ld_dword( fs->dirbuf + XDIR_FstClus );		/* Start cluster */
	obj->sizebytes		= ld_qword( fs->dirbuf + XDIR_FileSize );	/* Size */
	obj->chainstatus	= fs->dirbuf[XDIR_GenFlags] & FFS_CHAINSTATUS_NOFATCHAIN;				/* Allocation status */
	obj->sizeoflastfrag	= 0;										/* No last fragment info */
	}
/*============================================================================*/
/*------------------------------------------------*/
/* exFAT: Load the object's directory entry block */
/*------------------------------------------------*/
/*============================================================================*/
static ffs_fresult_t load_obj_xdir(
	ffs_directory_t		*dp,			/* Blank directory object to be used to access containing directory */
	const ffs_object_t	*obj	/* Object with its containing directory information */
)
	{
	ffs_fresult_t res;

	/* Open object containing directory */
	dp->obj->fs				= obj->fs;
	dp->obj->startcluster	= obj->parentdirstartcluster;
	dp->obj->chainstatus	= (uint8_t)obj->parentdirsize;
	dp->obj->sizebytes		= obj->parentdirsize & 0xFFFFFF00;
	dp->obj->sizeoflastfrag	= 0;
	dp->blk_ofs				= obj->offsetinparentdir;

	res = dir_sdi( dp, dp->blk_ofs );	/* Goto object's entry block */
	if( res == FR_OK )
		{
		res = load_xdir( dp );		/* Load the object's entry block */
		}
	return res;
	}
/*============================================================================*/
/*----------------------------------------*/
/* exFAT: Store the directory entry block */
/*----------------------------------------*/
/*============================================================================*/
static ffs_fresult_t store_xdir(
	ffs_directory_t	*dp				/* Pointer to the directory object */
)
	{
	ffs_fresult_t res;
	unsigned int nent;
	uint8_t *dirb = dp->obj->fs->dirbuf;	/* Pointer to the directory entry block 85+C0+C1s */

	/* Create set sum */
	st_word( dirb + XDIR_SetSum, xdir_sum( dirb ));
	nent = dirb[XDIR_NumSec] + 1;

	/* Store the directory entry block to the directory */
	res = dir_sdi( dp, dp->blk_ofs );
	while( res == FR_OK )
		{
		res = move_window( dp->obj->fs, dp->sect );
		if( res != FR_OK )
			break;
		memcpy( dp->dir, dirb, SIZE_OF_DIR_ENTRY );
		dp->obj->fs->windowisdirty = 1;
		if( --nent == 0 )
			break;
		dirb += SIZE_OF_DIR_ENTRY;
		res = dir_next( dp, 0 );
		}
	return ( res == FR_OK || res == FR_DISK_ERR ) ? res : FR_INT_ERR;
	}
/*============================================================================*/
/*-------------------------------------------*/
/* exFAT: Create a new directory entry block */
/*-------------------------------------------*/
/*============================================================================*/
static void create_xdir(
	uint8_t* dirb,			/* Pointer to the directory entry block buffer */
	const ffs_wchar_t* lfn	/* Pointer to the object name */
)
	{
	unsigned int i;
	uint8_t nc1, nlen;
	ffs_wchar_t wc;


	/* Create file-directory and stream-extension entry */
	memset( dirb, 0, 2 * SIZE_OF_DIR_ENTRY );
	dirb[0 * SIZE_OF_DIR_ENTRY + XDIR_Type] = ET_FILEDIR;
	dirb[1 * SIZE_OF_DIR_ENTRY + XDIR_Type] = ET_STREAM;

	/* Create file-name entries */
	i = SIZE_OF_DIR_ENTRY * 2;	/* Top of file_name entries */
	nlen = nc1 = 0;
	wc = 1;
	do
		{
		dirb[i++] = ET_FILENAME;
		dirb[i++] = 0;
		do  	/* Fill name field */
			{
			if( wc != 0 && ( wc = lfn[nlen] ) != 0 )
				nlen++;	/* Get a character if exist */
			st_word( dirb + i, wc ); 	/* Store it */
			i += 2;
			}
		while( i % SIZE_OF_DIR_ENTRY != 0 );
		nc1++;
		}
	while( lfn[nlen] );	/* Fill next entry if any char follows */

	dirb[XDIR_NumName] = nlen;		/* Set name length */
	dirb[XDIR_NumSec] = 1 + nc1;	/* Set secondary count (C0 + C1s) */
	st_word( dirb + XDIR_NameHash, xname_sum( lfn ));	/* Set name hash */
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Read an object from the directory                                     */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static ffs_fresult_t dir_read(
	ffs_directory_t	*dp,		/* Pointer to the directory object */
	int				vol			/* Filtered by 0:file/directory or 1:volume label */
)
	{
	ffs_fresult_t	res = FR_NO_FILE;
	ffs_volume_t	*fs = dp->obj->fs;
	uint8_t			attr;
	uint8_t			ord = 0xFF, sum = 0xFF;

	while( dp->sect )
		{
		uint8_t	b;

		res = move_window( fs, dp->sect );
		if( res != FR_OK )
			break;
		b = dp->dir[DIR_Name];	/* Test for the entry type */
		if( b == 0 )
			{
			res = FR_NO_FILE;
			break; /* Reached to end of the directory */
			}
		if( fs->fs_type == FFS_FILESYSTEMTYPE_EXFAT )  	/* On the exFAT volume */
			{
			if( vol )
				{
				if( b == ET_VLABEL )
					break;	/* Volume label entry? */
				}
			else
				{
				if( b == ET_FILEDIR )  		/* Start of the file entry block? */
					{
					dp->blk_ofs = dp->direntryoffset;	/* Get location of the block */
					res = load_xdir( dp );	/* Load the entry block */
					if( res == FR_OK )
						{
						dp->obj->attributes = fs->dirbuf[XDIR_Attr] & AM_MASK;	/* Get attribute */
						}
					break;
					}
				}
			}
		else
			{
			/* On the FAT/FAT32 volume */
			dp->obj->attributes = attr = dp->dir[DIR_Attr] & AM_MASK;	/* Get attribute */
			if( b == DDEM || b == '.' || (int)(( attr & ~FFS_FILEATTRIBUTE_ARCHIVE ) == AM_VOL ) != vol )  	/* An entry without valid data */
				ord = 0xFF;
			else
				{
				if( attr == AM_LFN )  	/* An LFN entry is found */
					{
					if( b & LLEF )  		/* Is it start of an LFN sequence? */
						{
						sum = dp->dir[LDIR_Chksum];
						b &= (uint8_t)~LLEF;
						ord = b;
						dp->blk_ofs = dp->direntryoffset;
						}
					/* Check LFN validity and capture it */
					ord = ( b == ord && sum == dp->dir[LDIR_Chksum] && pick_lfn( fs->lfnbuf, dp->dir )) ? ord - 1 : 0xFF;
					}
				else  				/* An SFN entry is found */
					{
					if( ord != 0 || sum != sum_sfn( dp->dir ))  	/* Is there a valid LFN? */
						{
						dp->blk_ofs = 0xFFFFFFFF;	/* It has no LFN. */
						}
					break;
					}
				}
			}
		res = dir_next( dp, 0 );		/* Next entry */
		if( res != FR_OK )
			break;
		}

	if( res != FR_OK )
		dp->sect = 0;		/* Terminate the read operation on error or EOT */
	return res;
	}
/*----------------------------------------------------------------------------*/
static inline ffs_fresult_t __attribute__(( always_inline )) dir_read_file( ffs_directory_t* dp )
	{
	return dir_read( dp, 0 );
	}
/*----------------------------------------------------------------------------*/
static inline ffs_fresult_t __attribute__(( always_inline )) dir_read_label( ffs_directory_t* dp )
	{
	return dir_read( dp, 1 );
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Directory handling - Find an object in the directory                  */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static ffs_fresult_t dir_find( /* FR_OK(0):succeeded, !=0:error */
	ffs_directory_t	*dp					/* Pointer to the directory object with the file name */
)
	{
	ffs_fresult_t	res;
	ffs_volume_t	*fs = dp->obj->fs;
	uint8_t			c;
	uint8_t			a, ord, sum;

	res = dir_sdi( dp, 0 );			/* Rewind directory object */
	if( res != FR_OK )
		return res;
	if( fs->fs_type == FFS_FILESYSTEMTYPE_EXFAT )  	/* On the exFAT volume */
		{
		uint8_t nc;
		unsigned int di, ni;
		uint16_t hash = xname_sum( fs->lfnbuf );		/* Hash value of the name to find */

		while(( res = dir_read_file( dp )) == FR_OK )  	/* Read an item */
			{
#if FFS_CONFIG_MAXIMUMLFN < 255
			if( fs->dirbuf[XDIR_NumName] > FFS_CONFIG_MAXIMUMLFN )
				continue;		/* Skip comparison if inaccessible object name */
#endif
			if( ld_word( fs->dirbuf + XDIR_NameHash ) != hash )
				continue;	/* Skip comparison if hash mismatched */
			for( nc = fs->dirbuf[XDIR_NumName], di = SIZE_OF_DIR_ENTRY * 2, ni = 0; nc; nc--, di += 2, ni++ )  	/* Compare the name */
				{
				if(( di % SIZE_OF_DIR_ENTRY ) == 0 )
					di += 2;
				if( ffs_wtoupper( ld_word( fs->dirbuf + di )) != ffs_wtoupper( fs->lfnbuf[ni] ))
					break;
				}
			if( nc == 0 && !fs->lfnbuf[ni] )
				break;	/* Name matched? */
			}
		return res;
		}
	/* On the FAT/FAT32 volume */
	ord = sum = 0xFF;
	dp->blk_ofs = 0xFFFFFFFF;	/* Reset LFN sequence */
	do
		{
		res = move_window( fs, dp->sect );
		if( res != FR_OK )
			break;
		c = dp->dir[DIR_Name];
		if( c == 0 )
			{
			res = FR_NO_FILE;    /* Reached to end of table */
			break;
			}
		dp->obj->attributes = a = dp->dir[DIR_Attr] & AM_MASK;
		if( c == DDEM || (( a & AM_VOL ) && a != AM_LFN ))  	/* An entry without valid data */
			{
			ord = 0xFF;
			dp->blk_ofs = 0xFFFFFFFF;	/* Reset LFN sequence */
			}
		else
			{
			if( a == AM_LFN )  			/* An LFN entry is found */
				{
				if( !( dp->fn[NSFLAG] & NS_NOLFN ))
					{
					if( c & LLEF )  		/* Is it start of LFN sequence? */
						{
						sum = dp->dir[LDIR_Chksum];
						c &= (uint8_t)~LLEF;
						ord = c;	/* LFN start order */
						dp->blk_ofs = dp->direntryoffset;	/* Start offset of LFN */
						}
					/* Check validity of the LFN entry and compare it with given name */
					ord = ( c == ord && sum == dp->dir[LDIR_Chksum] && cmp_lfn( fs->lfnbuf, dp->dir )) ? ord - 1 : 0xFF;
					}
				}
			else  					/* An SFN entry is found */
				{
				if( ord == 0 && sum == sum_sfn( dp->dir ))
					break;	/* LFN matched? */
				if( !( dp->fn[NSFLAG] & NS_LOSS ) && !memcmp( dp->dir, dp->fn, 11 ))
					break;	/* SFN matched? */
				ord = 0xFF;
				dp->blk_ofs = 0xFFFFFFFF;	/* Reset LFN sequence */
				}
			}
		res = dir_next( dp, 0 );	/* Next entry */
		}
	while( res == FR_OK );

	return res;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Register an object to the directory                                   */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static ffs_fresult_t dir_register( /* FR_OK:succeeded, FR_DENIED:no free entry or too many SFN collision, FR_DISK_ERR:disk error */
	ffs_directory_t	*dp						/* Target directory with object name to be created */
)
	{
	ffs_fresult_t	res;
	ffs_volume_t	*fs = dp->obj->fs;
	unsigned int	len, n_ent;
	uint8_t			sn[FFS_SFNBUFSIZE];


	if( dp->fn[NSFLAG] & ( NS_DOT | NS_NONAME ))
		return FR_INVALID_NAME;	/* Check name validity */
	for( len = 0; fs->lfnbuf[len]; len++ )	/* Get lfn length */
		{}

	if( fs->fs_type == FFS_FILESYSTEMTYPE_EXFAT )  	/* On the exFAT volume */
		{
		n_ent = ( len + 14 ) / 15 + 2;	/* Number of entries to allocate (85+C0+C1s) */
		res = dir_alloc( dp, n_ent );		/* Allocate directory entries */
		if( res != FR_OK )
			return res;
		dp->blk_ofs = dp->direntryoffset - SIZE_OF_DIR_ENTRY * ( n_ent - 1 );	/* Set the allocated entry block offset */

		if( dp->obj->chainstatus & FFS_CHAINSTATUS_NEEDSUPDATE )  			/* Has the directory been stretched by new allocation? */
			{
			dp->obj->chainstatus &= ~FFS_CHAINSTATUS_NEEDSUPDATE;
			res = fill_first_frag( dp->obj );	/* Fill the first fragment on the FAT if needed */
			if( res != FR_OK )
				return res;
			res = fill_last_frag( dp->obj, dp->clust, 0xFFFFFFFF );	/* Fill the last fragment on the FAT if needed */
			if( res != FR_OK )
				return res;
			if( dp->obj->startcluster != 0 )  		/* Is it a sub-directory? */
				{
				ffs_directory_t	dj;
				ffs_object_t	djobj;

				dj.obj			= &djobj;
				dj.objectidx	= INVALID_OBJECT_INDEX;
				dj.obj->fs		= fs;

				res = load_obj_xdir( &dj, dp->obj );	/* Load the object status */
				if( res != FR_OK )
					return res;
				dp->obj->sizebytes += (uint32_t)fs->sectorspercluster * fs->bytespersector;		/* Increase the directory size by cluster size */
				st_qword( fs->dirbuf + XDIR_FileSize, dp->obj->sizebytes );
				st_qword( fs->dirbuf + XDIR_ValidFileSize, dp->obj->sizebytes );
				fs->dirbuf[XDIR_GenFlags] = dp->obj->chainstatus | FFS_CHAINSTATUS_ALLOCATIONPOSSIBLE;		/* Update the allocation status */
				res = store_xdir( &dj );				/* Store the object status */
				if( res != FR_OK )
					return res;
				}
			}

		create_xdir( fs->dirbuf, fs->lfnbuf );	/* Create on-memory directory block to be written later */
		return FR_OK;
		}
	/* On the FAT/FAT32 volume */
	memcpy( sn, dp->fn, sizeof sn );
	if( sn[NSFLAG] & NS_LOSS )  			/* When LFN is out of 8.3 format, generate a numbered name */
		{
		unsigned int	n;

		dp->fn[NSFLAG] = NS_NOLFN;		/* Find only SFN */
		for( n = 1; n < 100; n++ )
			{
			gen_numname( dp->fn, sn, fs->lfnbuf, n );	/* Generate a numbered name */
			res = dir_find( dp );				/* Check if the name collides with existing SFN */
			if( res != FR_OK )
				break;
			}
		if( n == 100 )
			return FR_DENIED;		/* Abort if too many collisions */
		if( res != FR_NO_FILE )
			return res;	/* Abort if the result is other than 'not collided' */
		dp->fn[NSFLAG] = sn[NSFLAG];
		}

	/* Create an SFN with/without LFNs. */
	n_ent = ( sn[NSFLAG] & NS_LFN ) ? ( len + 12 ) / 13 + 1 : 1;	/* Number of entries to allocate */
	res = dir_alloc( dp, n_ent );		/* Allocate entries */
	if( res == FR_OK && --n_ent )  	/* Set LFN entry if needed */
		{
		res = dir_sdi( dp, dp->direntryoffset - n_ent * SIZE_OF_DIR_ENTRY );
		if( res == FR_OK )
			{
			uint8_t	sum;

			sum = sum_sfn( dp->fn );	/* Checksum value of the SFN tied to the LFN */
			do  					/* Store LFN entries in bottom first */
				{
				res = move_window( fs, dp->sect );
				if( res != FR_OK )
					break;
				put_lfn( fs->lfnbuf, dp->dir, (uint8_t)n_ent, sum );
				fs->windowisdirty = 1;
				res = dir_next( dp, 0 );	/* Next entry */
				}
			while( res == FR_OK && --n_ent );
			}
		}

	/* Set SFN entry */
	if( res == FR_OK )
		{
		res = move_window( fs, dp->sect );
		if( res == FR_OK )
			{
			memset( dp->dir, 0, SIZE_OF_DIR_ENTRY );	/* Clean the entry */
			memcpy( dp->dir + DIR_Name, dp->fn, 11 );	/* Put SFN */
			dp->dir[DIR_NTres] = dp->fn[NSFLAG] & ( NS_BODY | NS_EXT );	/* Put NT flag */
			fs->windowisdirty = 1;
			}
		}

	return res;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Remove an object from the directory                                   */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static ffs_fresult_t dir_remove( /* FR_OK:Succeeded, FR_DISK_ERR:A disk error */
	ffs_directory_t	*dp					/* Directory object pointing the entry to be removed */
)
	{
	ffs_fresult_t	res;
	ffs_volume_t	*fs = dp->obj->fs;
	uint32_t		last = dp->direntryoffset;

	res = ( dp->blk_ofs == 0xFFFFFFFF ) ? FR_OK : dir_sdi( dp, dp->blk_ofs );	/* Goto top of the entry block if LFN is exist */
	if( res == FR_OK )
		{
		do
			{
			res = move_window( fs, dp->sect );
			if( res != FR_OK )
				break;
			if( fs->fs_type == FFS_FILESYSTEMTYPE_EXFAT )  	/* On the exFAT volume */
				{
				dp->dir[XDIR_Type] &= 0x7F;	/* Clear the entry InUse flag. */
				}
			else  										/* On the FAT/FAT32 volume */
				{
				dp->dir[DIR_Name] = DDEM;	/* Mark the entry 'deleted'. */
				}
			fs->windowisdirty = 1;
			if( dp->direntryoffset >= last )
				break;	/* If reached last entry then all entries of the object has been deleted. */
			res = dir_next( dp, 0 );	/* Next entry */
			}
		while( res == FR_OK );
		if( res == FR_NO_FILE )
			res = FR_INT_ERR;
		}

	return res;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Get file information from directory entry                             */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static void get_fileinfo(
	ffs_directory_t	*dp,			/* Pointer to the directory object */
	ffs_fileinfo_t	*fno		/* Pointer to the file information to be filled */
)
	{
	unsigned int	si, di;
	uint8_t			lcf;
	ffs_wchar_t		wc, hs;
	ffs_volume_t	*fs = dp->obj->fs;
	unsigned int	nw;


	fno->fname[0] = 0;			/* Invaidate file info */
	if( dp->sect == 0 )
		{
		memset( fno, 0, sizeof *fno );
		return;	/* Exit if read pointer has reached end of directory */
		}

	if( fs->fs_type == FFS_FILESYSTEMTYPE_EXFAT )  	/* exFAT volume */
		{
		unsigned int nc = 0;

		si = SIZE_OF_DIR_ENTRY * 2;
		di = 0;	/* 1st C1 entry in the entry block */
		hs = 0;
		while( nc < fs->dirbuf[XDIR_NumName] )
			{
			if( si >= MAXDIRB( FFS_CONFIG_MAXIMUMLFN ))  	/* Truncated directory block? */
				{
				di = 0;
				break;
				}
			if(( si % SIZE_OF_DIR_ENTRY ) == 0 )
				si += 2;	/* Skip entry type field */
			wc = ld_word( fs->dirbuf + si );
			si += 2;
			nc++;	/* Get a character */
			if( hs == 0 && IsSurrogate( wc ))  	/* Is it a surrogate? */
				{
				hs = wc;
				continue;				/* Get low surrogate */
				}
			nw = put_utf((uint32_t)hs << 16 | wc, &fno->fname[di], FFS_CONFIG_MAXIMUMLFN - di );	/* Store it in API encoding */
			if( nw == 0 )  						/* Buffer overflow or wrong char? */
				{
				di = 0;
				break;
				}
			di += nw;
			hs = 0;
			}
		if( hs != 0 )
			di = 0;					/* Broken surrogate pair? */
		if( di == 0 )
			fno->fname[di++] = '\?';	/* Inaccessible object name? */
		fno->fname[di] = 0;						/* Terminate the name */
		fno->altname[0] = 0;					/* exFAT does not support SFN */

		fno->fattrib	= fs->dirbuf[XDIR_Attr] & AM_MASKX;		/* Attribute */
		fno->fsize		= ( fno->fattrib & FFS_FILEATTRIBUTE_DIRECTORY ) ? 0 : ld_qword( fs->dirbuf + XDIR_FileSize );	/* Size */
		fno->ftime		= ld_word( fs->dirbuf + XDIR_ModTime + 0 );	/* Time */
		fno->fdate		= ld_word( fs->dirbuf + XDIR_ModTime + 2 );	/* Date */
		return;
		}
	else
		{
		/* FAT/FAT32 volume */
		if( dp->blk_ofs != 0xFFFFFFFF )  	/* Get LFN if available */
			{
			si = di = 0;
			hs = 0;
			while( fs->lfnbuf[si] != 0 )
				{
				wc = fs->lfnbuf[si++];		/* Get an LFN character (UTF-16) */
				if( hs == 0 && IsSurrogate( wc ))  	/* Is it a surrogate? */
					{
					hs = wc;
					continue;		/* Get low surrogate */
					}
				nw = put_utf((uint32_t)hs << 16 | wc, &fno->fname[di], FFS_CONFIG_MAXIMUMLFN - di );	/* Store it in API encoding */
				if( nw == 0 )  				/* Buffer overflow or wrong char? */
					{
					di = 0;
					break;
					}
				di += nw;
				hs = 0;
				}
			if( hs != 0 )
				di = 0;	/* Broken surrogate pair? */
			fno->fname[di] = 0;		/* Terminate the LFN (null string means LFN is invalid) */
			}
		}

	si = di = 0;
	while( si < 11 )  		/* Get SFN from SFN entry */
		{
		wc = dp->dir[si++];			/* Get a char */
		if( wc == ' ' )
			continue;	/* Skip padding spaces */
		if( wc == RDDEM )
			wc = DDEM;	/* Restore replaced DDEM character */
		if( si == 9 && di < FFS_SFNBUFSIZE )
			fno->altname[di++] = '.';	/* Insert a . if extension is exist */
#if FFS_CONFIG_LFNUNICODE >= 1	/* Unicode output */
		if( dbc_1st((uint8_t)wc ) && si != 8 && si != 11 && dbc_2nd( dp->dir[si] ))  	/* Make a DBC if needed */
			wc = wc << 8 | dp->dir[si++];
		wc = ffs_oem2uni( wc, FFS_CodePage );		/* ANSI/OEM -> Unicode */
		if( wc == 0 )  				/* Wrong char in the current code page? */
			{
			di = 0;
			break;
			}
		nw = put_utf( wc, &fno->altname[di], FFS_SFNBUFSIZE - di );	/* Store it in API encoding */
		if( nw == 0 )  				/* Buffer overflow? */
			{
			di = 0;
			break;
			}
		di += nw;
#else					/* ANSI/OEM output */
		fno->altname[di++] = (ffs_char_t)wc;	/* Store it without any conversion */
#endif
		}
	fno->altname[di] = 0;	/* Terminate the SFN  (null string means SFN is invalid) */

	if( fno->fname[0] == 0 )  	/* If LFN is invalid, altname[] needs to be copied to fname[] */
		{
		if( di == 0 )  	/* If LFN and SFN both are invalid, this object is inaccessible */
			fno->fname[di++] = '\?';
		else
			{
			for( si = di = 0, lcf = NS_BODY; fno->altname[si]; si++, di++ )  	/* Copy altname[] to fname[] with case information */
				{
				wc = (ffs_wchar_t)fno->altname[si];
				if( wc == '.' )
					lcf = NS_EXT;
				if( IsUpper( wc ) && ( dp->dir[DIR_NTres] & lcf ))
					wc += 0x20;
				fno->fname[di] = (ffs_char_t)wc;
				}
			}
		fno->fname[di] = 0;	/* Terminate the LFN */
		if( !dp->dir[DIR_NTres] )
			fno->altname[0] = 0;	/* Altname is not needed if neither LFN nor case info is exist. */
		}

	fno->fattrib	= dp->dir[DIR_Attr] & AM_MASK;			/* Attribute */
	fno->fsize		= ld_dword( dp->dir + DIR_FileSize );	/* Size */
	fno->ftime		= ld_word( dp->dir + DIR_ModTime + 0 );	/* Time */
	fno->fdate		= ld_word( dp->dir + DIR_ModTime + 2 );	/* Date */
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Pattern matching                                                      */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
#define FIND_RECURS	4	/* Maximum number of wildcard terms in the pattern to limit recursion */
/*============================================================================*/
static uint32_t get_achar( /* Get a character and advance ptr */
	const ffs_char_t** ptr		/* Pointer to pointer to the ANSI/OEM or Unicode string */
)
	{
	uint32_t chr;

#if FFS_CONFIG_LFNUNICODE >= 1	/* Unicode input */
	chr = tchar_t2uni( ptr );
	if( chr == 0xFFFFFFFF )
		chr = 0;		/* Wrong UTF encoding is recognized as end of the string */
	chr = ffs_wtoupper( chr );

#else									/* ANSI/OEM input */
	chr = (uint8_t) * ( *ptr )++;				/* Get a byte */
	if( IsLower( chr ))
		chr -= 0x20;		/* To upper ASCII char */
	if( FFS_ExCvt && chr >= 0x80 )
		chr = FFS_ExCvt[chr - 0x80];	/* To upper SBCS extended char */
	if( dbc_1st((uint8_t)chr ))  	/* Get DBC 2nd byte if needed */
		chr = dbc_2nd((uint8_t)**ptr ) ? chr << 8 | (uint8_t) * ( *ptr )++ : 0;

#endif
	return chr;
	}
/*============================================================================*/
static int pattern_match( /* 0:mismatched, 1:matched */
	const ffs_char_t* pat,	/* Matching pattern */
	const ffs_char_t* nam,	/* String to be tested */
	unsigned int skip,			/* Number of pre-skip chars (number of ?s, b8:infinite (* specified)) */
	unsigned int recur			/* Recursion count */
)
	{
	const ffs_char_t *pptr;
	const ffs_char_t *nptr;
	uint32_t pchr, nchr;
	unsigned int sk;


	while(( skip & 0xFF ) != 0 )  		/* Pre-skip name chars */
		{
		if( !get_achar( &nam ))
			return 0;	/* Branch mismatched if less name chars */
		skip--;
		}
	if( *pat == 0 && skip )
		return 1;	/* Matched? (short circuit) */

	do
		{
		pptr = pat;
		nptr = nam;			/* Top of pattern and name to match */
		while( 1 )
			{
			if( *pptr == '\?' || *pptr == '*' )  	/* Wildcard term? */
				{
				if( recur == 0 )
					return 0;	/* Too many wildcard terms? */
				sk = 0;
				do  	/* Analyze the wildcard term */
					{
					if( *pptr++ == '\?' )
						sk++;
					else
						sk |= 0x100;
					}
				while( *pptr == '\?' || *pptr == '*' );
				if( pattern_match( pptr, nptr, sk, recur - 1 ))
					return 1;	/* Test new branch (recursive call) */
				nchr = *nptr;
				break;	/* Branch mismatched */
				}
			pchr = get_achar( &pptr );	/* Get a pattern char */
			nchr = get_achar( &nptr );	/* Get a name char */
			if( pchr != nchr )
				break;	/* Branch mismatched? */
			if( pchr == 0 )
				return 1;	/* Branch matched? (matched at end of both strings) */
			}
		get_achar( &nam );			/* nam++ */
		}
	while( skip && nchr );		/* Retry until end of name if infinite search is specified */

	return 0;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Pick a top segment and create the object name in directory form       */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static ffs_fresult_t create_name( /* FR_OK: successful, FR_INVALID_NAME: could not create */
	ffs_directory_t		*dp,					/* Pointer to the directory object */
	const ffs_char_t	** path			/* Pointer to pointer to the segment in the path string */
)
	{
	uint8_t b, cf;
	ffs_wchar_t wc;
	ffs_wchar_t *lfn;
	const ffs_char_t* p;
	unsigned int i, ni, si, di;


	/* Create LFN into LFN working buffer */
	p = *path;
	lfn = dp->obj->fs->lfnbuf;
	di = 0;
	while( 1 )
		{
		uint32_t uc;

		uc = tchar_t2uni( &p );			/* Get a character */
		if( uc == 0xFFFFFFFF )
			return FR_INVALID_NAME;		/* Invalid code or UTF decode error */
		if( uc >= 0x10000 )
			lfn[di++] = (ffs_wchar_t)( uc >> 16 );	/* Store high surrogate if needed */
		wc = (ffs_wchar_t)uc;
		if( wc < ' ' || IsSeparator( wc ))
			break;	/* Break if end of the path or a separator is found */
		if( wc < 0x80 && strchr( "*:<>|\"\?\x7F", (int)wc ))
			return FR_INVALID_NAME;	/* Reject illegal characters for LFN */
		if( di >= FFS_CONFIG_MAXIMUMLFN )
			return FR_INVALID_NAME;	/* Reject too long name */
		lfn[di++] = wc;				/* Store the Unicode character */
		}
	if( wc < ' ' )  				/* Stopped at end of the path? */
		{
		cf = NS_LAST;			/* Last segment */
		}
	else  					/* Stopped at a separator */
		{
		while( IsSeparator( *p ))
			p++;	/* Skip duplicated separators if exist */
		cf = 0;					/* Next segment may follow */
		if( IsTerminator( *p ))
			cf = NS_LAST;	/* Ignore terminating separator */
		}
	*path = p;					/* Return pointer to the next segment */

	if(( di == 1 && lfn[di - 1] == '.' ) ||
			( di == 2 && lfn[di - 1] == '.' && lfn[di - 2] == '.' ))  	/* Is this segment a dot name? */
		{
		lfn[di] = 0;
		for( i = 0; i < 11; i++ )  	/* Create dot name for SFN entry */
			dp->fn[i] = ( i < di ) ? '.' : ' ';
		dp->fn[i] = cf | NS_DOT;	/* This is a dot entry */
		return FR_OK;
		}
	while( di )  					/* Snip off trailing spaces and dots if exist */
		{
		wc = lfn[di - 1];
		if( wc != ' ' && wc != '.' )
			break;
		di--;
		}
	lfn[di] = 0;							/* LFN is created into the working buffer */
	if( di == 0 )
		return FR_INVALID_NAME;	/* Reject null name */

	/* Create SFN in directory form */
	for( si = 0; lfn[si] == ' '; si++ )	/* Remove leading spaces */
		{}
	if( si > 0 || lfn[si] == '.' )
		cf |= NS_LOSS | NS_LFN;	/* Is there any leading space or dot? */
	while( di > 0 && lfn[di - 1] != '.' )
		di--;	/* Find last dot (di<=si: no extension) */

	memset( dp->fn, ' ', 11 );
	i = b = 0;
	ni = 8;
	while( 1 )
		{
		wc = lfn[si++];					/* Get an LFN character */
		if( wc == 0 )
			break;				/* Break on end of the LFN */
		if( wc == ' ' || ( wc == '.' && si != di ))  	/* Remove embedded spaces and dots */
			{
			cf |= NS_LOSS | NS_LFN;
			continue;
			}

		if( i >= ni || si == di )  		/* End of field? */
			{
			if( ni == 11 )  				/* Name extension overflow? */
				{
				cf |= NS_LOSS | NS_LFN;
				break;
				}
			if( si != di )
				cf |= NS_LOSS | NS_LFN;	/* Name body overflow? */
			if( si > di )
				break;						/* No name extension? */
			si = di;
			i = 8;
			ni = 11;
			b <<= 2;		/* Enter name extension */
			continue;
			}

		if( wc >= 0x80 )  	/* Is this an extended character? */
			{
			cf |= NS_LFN;	/* LFN entry needs to be created */
			if( FFS_ExCvt )  	/* In SBCS cfg */
				{
				wc = ffs_uni2oem( wc, FFS_CodePage );			/* Unicode ==> ANSI/OEM code */
				if( wc & 0x80 )
					wc = FFS_ExCvt[wc & 0x7F];	/* Convert extended character to upper (SBCS) */
				}
			else  		/* In DBCS cfg */
				{
				wc = ffs_uni2oem( ffs_wtoupper( wc ), FFS_CodePage );	/* Unicode ==> Up-convert ==> ANSI/OEM code */
				}
			}

		if( wc >= 0x100 )  				/* Is this a DBC? */
			{
			if( i >= ni - 1 )  			/* Field overflow? */
				{
				cf |= NS_LOSS | NS_LFN;
				i = ni;
				continue;		/* Next field */
				}
			dp->fn[i++] = (uint8_t)( wc >> 8 );	/* Put 1st byte */
			}
		else  						/* SBC */
			{
			if( wc == 0 || strchr( "+,;=[]", (int)wc ))  	/* Replace illegal characters for SFN */
				{
				wc = '_';
				cf |= NS_LOSS | NS_LFN;/* Lossy conversion */
				}
			else
				{
				if( IsUpper( wc ))  		/* ASCII upper case? */
					b |= 2;
				if( IsLower( wc ))  		/* ASCII lower case? */
					{
					b |= 1;
					wc -= 0x20;
					}
				}
			}
		dp->fn[i++] = (uint8_t)wc;
		}

	if( dp->fn[0] == DDEM )
		dp->fn[0] = RDDEM;	/* If the first character collides with DDEM, replace it with RDDEM */

	if( ni == 8 )
		b <<= 2;				/* Shift capital flags if no extension */
	if(( b & 0x0C ) == 0x0C || ( b & 0x03 ) == 0x03 )
		cf |= NS_LFN;	/* LFN entry needs to be created if composite capitals */
	if( !( cf & NS_LFN ))  				/* When LFN is in 8.3 format without extended character, NT flags are created */
		{
		if( b & 0x01 )
			cf |= NS_EXT;		/* NT flag (Extension has small capital letters only) */
		if( b & 0x04 )
			cf |= NS_BODY;	/* NT flag (Body has small capital letters only) */
		}

	dp->fn[NSFLAG] = cf;	/* SFN is created into dp->fn[] */

	return FR_OK;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Follow a file path                                                    */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static ffs_fresult_t follow_path( /* FR_OK(0): successful, !=0: error code */
	ffs_directory_t		*dp,					/* Directory object to return last directory and found object */
	const ffs_char_t	*path			/* Full-path string to find a file or directory */
)
	{
	ffs_fresult_t	res;
	ffs_volume_t	*fs = dp->obj->fs;


	if( !IsSeparator( *path )) //@@@@ && ( FF_STR_VOLUME_ID != 2 || !IsTerminator( *path )))  	/* Without heading separator */
		{
		dp->obj->startcluster = fs->curdirstartcluster;		/* Start at the current directory */
		}
	else
		{
		/* With heading separator */
		while( IsSeparator( *path ))
			path++;	/* Strip separators */
		dp->obj->startcluster = 0;					/* Start from the root directory */
		}
	dp->obj->sizeoflastfrag = 0;	/* Invalidate last fragment counter of the object */
	if( fs->fs_type == FFS_FILESYSTEMTYPE_EXFAT && dp->obj->startcluster )  	/* exFAT: Retrieve the sub-directory's status */
		{
		ffs_directory_t dj;
		ffs_object_t	djobj;
		
		dj.obj			= &djobj;
		dj.objectidx	= INVALID_OBJECT_INDEX;
		dj.obj->fs		= fs;

		dp->obj->parentdirstartcluster	= fs->parentdirstartcluster;
		dp->obj->parentdirsize			= fs->parentdirsize;
		dp->obj->offsetinparentdir		= fs->offsetinparentdir;

		res = load_obj_xdir( &dj, dp->obj );
		if( res != FR_OK )
			return res;
		dp->obj->sizebytes		= ld_dword( fs->dirbuf + XDIR_FileSize );
		dp->obj->chainstatus	= fs->dirbuf[XDIR_GenFlags] & FFS_CHAINSTATUS_NOFATCHAIN;
		}

	if((unsigned int)*path < ' ' )  				/* Null path name is the origin directory itself */
		{
		dp->fn[NSFLAG] = NS_NONAME;
		res = dir_sdi( dp, 0 );

		}
	else  								/* Follow path */
		{
		while( 1 )
			{
			uint8_t	ns;

			res = create_name( dp, &path );	/* Get a segment name of the path */
			if( res != FR_OK )
				break;
			res = dir_find( dp );				/* Find an object with the segment name */
			ns = dp->fn[NSFLAG];
			if( res != FR_OK )  				/* Failed to find the object */
				{
				if( res == FR_NO_FILE )  	/* Object is not found */
					{
					if( ns & NS_DOT )  	/* If dot entry is not exist, stay there */
						{
						if( !( ns & NS_LAST ))
							continue;	/* Continue to follow if not last segment */
						dp->fn[NSFLAG] = NS_NONAME;
						res = FR_OK;
						}
					else  							/* Could not find the object */
						{
						if( !( ns & NS_LAST ))
							res = FR_NO_PATH;	/* Adjust error code if not last segment */
						}
					}
				break;
				}
			if( ns & NS_LAST )
				break;		/* Last segment matched. Function completed. */
			/* Get into the sub-directory */
			if( !( dp->obj->attributes & FFS_FILEATTRIBUTE_DIRECTORY ))  	/* It is not a sub-directory and cannot follow */
				{
				res = FR_NO_PATH;
				break;
				}
			if( fs->fs_type == FFS_FILESYSTEMTYPE_EXFAT )  	/* Save containing directory information for next dir */
				{
				dp->obj->parentdirstartcluster	= dp->obj->startcluster;
				dp->obj->parentdirsize			= ((uint32_t)dp->obj->sizebytes & 0xFFFFFF00 ) | dp->obj->chainstatus;
				dp->obj->offsetinparentdir = dp->blk_ofs;
				init_alloc_info( fs, dp->obj );	/* Open next directory */
				}
			else
				{
				dp->obj->startcluster = ld_clust( fs, fs->win + dp->direntryoffset % fs->bytespersector );	/* Open next directory */
				}
			}
		}

	return res;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* GPT support functions                                                      */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
/* Calculate CRC32 in byte-by-byte */

static uint32_t crc32( /* Returns next CRC value */
	uint32_t crc,			/* Current CRC value */
	uint8_t d				/* A byte to be processed */
)
	{
	uint8_t b;


	for( b = 1; b; b <<= 1 )
		{
		crc ^= ( d & b ) ? 1 : 0;
		crc = ( crc & 1 ) ? crc >> 1 ^ 0xEDB88320 : crc >> 1;
		}
	return crc;
	}
/*============================================================================*/
/* Check validity of GPT header */
/*============================================================================*/
static int test_gpt_header( /* 0:Invalid, 1:Valid */
	const uint8_t* gpth			/* Pointer to the GPT header */
)
	{
	unsigned int i;
	uint32_t bcc, hlen;


	if( memcmp( gpth + GPTH_Sign, "EFI PART" "\0\0\1", 12 ))
		return 0;	/* Check signature and version (1.0) */
	hlen = ld_dword( gpth + GPTH_Size );						/* Check header size */
	if( hlen < 92 || hlen > FFS_CONFIG_MINIMUMSECTORSIZE )
		return 0;
	for( i = 0, bcc = 0xFFFFFFFF; i < hlen; i++ )  			/* Check header BCC */
		bcc = crc32( bcc, i - GPTH_Bcc < 4 ? 0 : gpth[i] );
	if( ~bcc != ld_dword( gpth + GPTH_Bcc ))
		return 0;
	if( ld_dword( gpth + GPTH_PteSize ) != SZ_GPTE )
		return 0;	/* Table entry size (must be SZ_GPTE bytes) */
	if( ld_dword( gpth + GPTH_PtNum ) > 128 )
		return 0;		/* Table size (must be 128 entries or less) */

	return 1;
	}
/*============================================================================*/
/* Generate random value */
/*============================================================================*/
static uint32_t make_rand(
	uint32_t seed,		/* Seed value */
	uint8_t *buff,		/* Output buffer */
	unsigned int n			/* Data length */
)
	{
	unsigned int r;


	if( seed == 0 )
		seed = 1;
	do
		{
		for( r = 0; r < 8; r++ )
			seed = seed & 1 ? seed >> 1 ^ 0xA3000000 : seed >> 1;	/* Shift 8 bits the 32-bit LFSR */
		*buff++ = (uint8_t)seed;
		}
	while( --n );
	return seed;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Get logical drive number from path name                                    */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static int ParseLogicalDriveName( const ffs_char_t **path, ffs_char_t *drivename, int namelength )
	{
	const ffs_char_t	*p;
	ffs_char_t			*q;
	int					i, j;

	if( drivename == NULL || namelength < 2 )
		return -1;
	
	if( path == NULL || *path == NULL )
		{
		*drivename	= '\0';
		return 0;
		}

	// Let's copy the possible drive name to 'drivename'.
	for( i = 0, p = *path, q = drivename; i < namelength - 1 && isalnum( (int)*p ); i++, p++, q++ )
		*q	= *p;

	/* Skip all characters until we find a separator. */
	for( j = i; *p != '\0' && *p != ':' && *p != '\\' && *p != '/'; j++, p++ )
		{}

	/* There is no colon in the path, it does not have a drive name... */
	if( *p != ':' )
		{
		/* ...make the drive name empty... */
		*drivename	= '\0';
		/* ...and return a status saying that we did not find a drive name. */
		return 0;
		}

	/* Skip the colon in the path. */
	p++;

	/* The volume name is too long... */
	if( j != i )
		{
		/* ...make the drive name empty... */
		*drivename	= '\0';
		/* ...and return an error status. */
		return -1;
		}

	/* Finish the drive name string. */
	*q		= '\0';
	*path	= p;

	/* Return a status telling that we found a drive name. */
	return 1;
	}
/*============================================================================*/
static int ParsePhysicalDriveName( const ffs_char_t *pdn, ffs_char_t *phys, int namelength, int *Partition )
	{
	const ffs_char_t	*p;
	ffs_char_t			*q;
	int					i;

	// Let's copy the possible drive name to 'DriveName'.
	for( i = 0, p = pdn, q = phys; i < namelength - 1 && isalpha( (int)*p ); i++, p++, q++ )
		*q	= *p;
	
	// The drive name is empty or too long...
	if( i == 0 || i >= namelength - 1 )
		// ...return a failure status.
		return -1;

	i	= -1;
	// It seems that we found a partition number too...
	if( isdigit( (int)*p ))
		{
		// ...get the value.
		i	= *p++ - '0';
		// The partition number is invalid...
		if( i > 3 )
			// ...return a failure status.
			return -1;
		}

	// Either we don't have a colon or there is garbage at the end...
	if( *p != '\0' && ( *p != ':' || *++p != '\0' ))
		// ...we will reject the data.
		return -1;

	// Let's finish our string.
	*q	= '\0';

	// Store the partition number...
	*Partition	= i;

	return 1;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Load a sector and check if it is an FAT VBR                           */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
#define	BOOTSECTOR_FORMAT_FAT		0
#define	BOOTSECTOR_FORMAT_EXFAT		1
#define	BOOTSECTOR_FORMAT_OTHER		2
#define	BOOTSECTOR_FORMAT_INVALID	3
#define	BOOTSECTOR_FORMAT_ERROR		4
/*============================================================================*/

/* 0:FAT/FAT32 VBR, 1:exFAT VBR, 2:Not FAT and valid BS, 3:Not FAT and invalid BS, 4:Disk error */
/* Filesystem object */
/* Sector to load and check if it is an FAT-VBR or not */

/* Check what the sector is */

static unsigned int check_fs( ffs_volume_t* fs, lba_t sect )
	{
	uint16_t	sign;
	uint8_t		b;

	fs->windowisdirty	= 0;
	fs->sectorinwin	= (lba_t)-1;				/* Invaidate window */

	if( move_window( fs, sect ) != FR_OK )
		return BOOTSECTOR_FORMAT_ERROR;		/* Load the boot sector */

	sign = ld_word( fs->win + BS_55AA );

	if( sign == 0xAA55 && !memcmp( fs->win + BS_JmpBoot, "\xEB\x76\x90" "EXFAT   ", 11 ))
		return BOOTSECTOR_FORMAT_EXFAT;		/* It is an exFAT VBR */

	b = fs->win[BS_JmpBoot];

	if( b == 0xEB || b == 0xE9 || b == 0xE8 )  	/* Valid JumpBoot code? (short jump, near jump or near call) */
		{
		uint16_t	w;

		if( sign == 0xAA55 && !memcmp( fs->win + BS_FilSysType32, "FAT32   ", 8 ))
			return BOOTSECTOR_FORMAT_FAT;	/* It is an FAT32 VBR */

		/* FAT volumes formatted with early MS-DOS lack BS_55AA and BS_FilSysType, so FAT VBR needs to be identified without them. */
		w = ld_word( fs->win + BPB_BytsPerSec );
		b = fs->win[BPB_SecPerClus];
		if(( w & ( w - 1 )) == 0 && w >= FFS_CONFIG_MINIMUMSECTORSIZE && w <= FFS_CONFIG_MAXIMUMSECTORSIZE	/* Properness of sector size (512-4096 and 2^n) */
			&& b != 0 && ( b & ( b - 1 )) == 0							/* Properness of cluster size (2^n) */
			&& ld_word( fs->win + BPB_RsvdSecCnt ) != 0					/* Properness of reserved sectors (MNBZ) */
			&& (unsigned int)fs->win[BPB_NumFATs] - 1 <= 1				/* Properness of FATs (1 or 2) */
			&& ld_word( fs->win + BPB_RootEntCnt ) != 0					/* Properness of root dir entries (MNBZ) */
			&& ( ld_word( fs->win + BPB_TotSec16 ) >= 128 || ld_dword( fs->win + BPB_TotSec32 ) >= 0x10000 )	/* Properness of volume sectors (>=128) */
			&& ld_word( fs->win + BPB_FATSz16 ) != 0 )  				/* Properness of FAT size (MNBZ) */

			return BOOTSECTOR_FORMAT_FAT;	/* It can be presumed an FAT VBR */
		}
	return sign == 0xAA55 ? BOOTSECTOR_FORMAT_OTHER : BOOTSECTOR_FORMAT_ERROR;	/* Not an FAT VBR (valid or invalid BS) */
	}
/*============================================================================*/
/* Find an FAT volume */
/* (It supports only generic partitioning rules, MBR, GPT and SFD) */
/*============================================================================*/
#define	MAX_MBR_ENTRIES	4
/*============================================================================*/

/* Returns BS status found in the hosting drive */
/* Filesystem object */
/* Partition to find < 0:find as SFD and partitions, >= 0:forced partition number */

static unsigned int find_volume( ffs_volume_t* fs, int part )
	{
	unsigned int	fmt, i;
	uint32_t		mbr_pt[MAX_MBR_ENTRIES];

	fmt	= check_fs( fs, 0 );				/* Load sector 0 and check if it is an FAT VBR as SFD format */
	if( fmt != BOOTSECTOR_FORMAT_OTHER && ( fmt >= BOOTSECTOR_FORMAT_INVALID || part <= 0 ))
		return fmt;	/* Returns if it is an FAT VBR as auto scan, not a BS or disk error */

	/* Sector 0 is not an FAT VBR or forced partition number wants a partition */

	if( fs->win[MBR_Table + PTE_System] == 0xEE )  	/* GPT protective MBR? */
		{
		uint64_t	pt_lba;
		uint32_t	n_ent;
		int			v_ent;

		if( move_window( fs, 1 ) != FR_OK )
			return BOOTSECTOR_FORMAT_ERROR;	/* Load GPT header sector (next to MBR) */

		if( !test_gpt_header( fs->win ))
			return BOOTSECTOR_FORMAT_INVALID;	/* Check if GPT header is valid */

		n_ent	= ld_dword( fs->win + GPTH_PtNum );		/* Number of entries */
		pt_lba	= ld_qword( fs->win + GPTH_PtOfs );		/* Table location */

		for( v_ent = -1, i = 0; i < n_ent; i++ )  			/* Find FAT partition */
			{
			uint32_t	ofs;

			if( move_window( fs, pt_lba + i * SZ_GPTE / fs->bytespersector ) != FR_OK )
				return BOOTSECTOR_FORMAT_ERROR;	/* PT sector */

			ofs = i * SZ_GPTE % fs->bytespersector;												/* Offset in the sector */
			if( !memcmp( fs->win + ofs + GPTE_PtGuid, GUID_MS_Basic, 16 ))  	/* MS basic data partition? */
				{
				v_ent++;
				fmt = check_fs( fs, ld_qword( fs->win + ofs + GPTE_FstLba ));	/* Load VBR and check status */
				if( part < 0 && fmt <= BOOTSECTOR_FORMAT_EXFAT )
					return fmt;			/* Auto search (valid FAT volume found first) */
				if( part >= 0 && v_ent == part )
					return fmt;		/* Forced partition order (regardless of it is valid or not) */
				}
			}
		return BOOTSECTOR_FORMAT_INVALID;	/* Not found */
		}

	if( part >= MAX_MBR_ENTRIES )
		return BOOTSECTOR_FORMAT_INVALID;	/* MBR has 4 partitions max */

	for( i = 0; i < MAX_MBR_ENTRIES; i++ )  		/* Load partition offset in the MBR */
		mbr_pt[i] = ld_dword( fs->win + MBR_Table + i * SZ_PTE + PTE_StLba );

	i = part >= 0 ? part : 0;		/* Table index to find first */
	do  							/* Find an FAT volume */
		{
		fmt = mbr_pt[i] ? check_fs( fs, mbr_pt[i] ) : BOOTSECTOR_FORMAT_INVALID;	/* Check if the partition is FAT */
		}
	while( part < 0 && fmt >= BOOTSECTOR_FORMAT_OTHER && ++i < MAX_MBR_ENTRIES );

	return fmt;
	}
/*============================================================================*/
static /*inline*/ int __attribute__((noinline)) SetErrNo( int errnovalue, int returnvalue )
	{
	errno	= errnovalue;
	return returnvalue;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Determine logical drive number and mount the volume if needed         */
/*----------------------------------------------------------------------------*/
/*============================================================================*/

/* FR_OK(0): successful, !=0: an error occurred */
/* Pointer to pointer to the path name (drive number) */
/* Pointer to pointer to the found filesystem object */
/* Desiered access mode to check write protection */

static int mount_volume( ffs_volume_t	*fs, uint8_t mode )
	{
	lba_t				bsect;
	uint32_t			tsect, sysect, fasize, nclst, szbfat;
	unsigned int		fmt;
	uint16_t			nrsv;
	ffs_diskstatus_t	stat;

	mode &= _FWRITE;								/* Desired access mode, write access or not */
	if( fs->fs_type != FFS_FILESYSTEMTYPE_INVALID ) /* If the volume has been mounted */
		{
		stat = disk_status( fs->pdrv );
		if( !( stat & STA_NOINIT ))  				/* and the physical drive is kept initialized */
			{
			if( mode && ( stat & STA_PROTECT ))  	/* Check write protection if needed */
				return SetErrNo( ENOMEM, -1 );	//FIXME: FR_WRITE_PROTECTED;
			return 0;							/* The filesystem object is already valid */
			}
		}

	fs->fs_type = FFS_FILESYSTEMTYPE_INVALID;		/* Invalidate the filesystem object */

	stat = disk_initialize( fs->pdrv );				/* Initialize the volume hosting physical drive */
	if( stat & STA_NOINIT )   						/* Check if the initialization succeeded */
		return SetErrNo( ENOMEM, -1 );	//FIXME: FR_NOT_READY;						/* Failed to initialize due to no medium or hard error */

	if( mode && ( stat & STA_PROTECT )) 			/* Check disk write protection if needed */
		return SetErrNo( ENOMEM, -1 );	//FIXME: FR_WRITE_PROTECTED;

	if( disk_ioctl( fs->pdrv, GET_SECTOR_SIZE, &fs->bytespersector ) != RES_OK )
		return SetErrNo( ENOMEM, -1 );	//FIXME: FR_DISK_ERR;
	if( fs->bytespersector > FFS_CONFIG_MAXIMUMSECTORSIZE || fs->bytespersector < FFS_CONFIG_MINIMUMSECTORSIZE || ( fs->bytespersector & ( fs->bytespersector - 1 )))
		return SetErrNo( ENOMEM, -1 );	//FIXME: FR_DISK_ERR;

	/* Find an FAT volume on the hosting drive */
	fmt = find_volume( fs, fs->partition );
	if( fmt >= BOOTSECTOR_FORMAT_OTHER )
		{
		if( fmt == BOOTSECTOR_FORMAT_ERROR )
			return SetErrNo( ENOMEM, -1 );	//FIXME: FR_DISK_ERR;					/* An error occurred in the disk I/O layer */
		return SetErrNo( ENOMEM, -1 );	//FIXME: FR_NO_FILESYSTEM;				/* No FAT volume is found */
		}

	bsect = fs->sectorinwin;						/* Volume offset in the hosting physical drive */

	/* An FAT volume is found (bsect). Following code initializes the filesystem object */

	if( fmt == BOOTSECTOR_FORMAT_EXFAT )
		{
		uint64_t maxlba;
		uint32_t so, bcl, i;

		for( i = BPB_ZeroedEx; i < BPB_ZeroedEx + 53 && fs->win[i] == 0; i++ )	/* Check zero filler */
			{}
		if( i < BPB_ZeroedEx + 53 )
			return SetErrNo( ENOMEM, -1 );	//FIXME: FR_NO_FILESYSTEM;

		if( ld_word( fs->win + BPB_FSVerEx ) != 0x100 )
			return SetErrNo( ENOMEM, -1 );	//FIXME: FR_NO_FILESYSTEM;	/* Check exFAT version (must be version 1.0) */

		if( 1 << fs->win[BPB_BytsPerSecEx] != fs->bytespersector )  	/* (BPB_BytsPerSecEx must be equal to the physical sector size) */
			return SetErrNo( ENOMEM, -1 );	//FIXME: FR_NO_FILESYSTEM;

		maxlba = ld_qword( fs->win + BPB_TotSecEx ) + bsect;	/* Last LBA of the volume + 1 */
		if( maxlba >= 0x100000000 )
			return SetErrNo( ENOMEM, -1 );	//FIXME: FR_NO_FILESYSTEM;	/* (It cannot be accessed in 32-bit LBA) */

		fs->sectorsperfat = ld_dword( fs->win + BPB_FatSzEx );	/* Number of sectors per FAT */

		fs->fatcopies = fs->win[BPB_NumFATsEx];			/* Number of FATs */
		if( fs->fatcopies != 1 )
			return SetErrNo( ENOMEM, -1 );	//FIXME: FR_NO_FILESYSTEM;	/* (Supports only 1 FAT) */

		fs->sectorspercluster = 1 << fs->win[BPB_SecPerClusEx];		/* Cluster size */
		if( fs->sectorspercluster == 0 )
			return SetErrNo( ENOMEM, -1 );	//FIXME: FR_NO_FILESYSTEM;	/* (Must be 1..32768 sectors) */

		nclst = ld_dword( fs->win + BPB_NumClusEx );		/* Number of clusters */
		if( nclst > MAX_EXFAT )
			return SetErrNo( ENOMEM, -1 );	//FIXME: FR_NO_FILESYSTEM;	/* (Too many clusters) */
		fs->fatentriescount = nclst + FIRST_VALID_CLUSTER;

		/* Boundaries and Limits */
		fs->volbase = bsect;
		fs->database = bsect + ld_dword( fs->win + BPB_DataOfsEx );
		fs->fatbase = bsect + ld_dword( fs->win + BPB_FatOfsEx );
		if( maxlba < (uint64_t)fs->database + nclst * fs->sectorspercluster )
			return SetErrNo( ENOMEM, -1 );	//FIXME: FR_NO_FILESYSTEM;	/* (Volume size must not be smaller than the size required) */
		fs->dirbase = ld_dword( fs->win + BPB_RootClusEx );

		/* Get bitmap location and check if it is contiguous (implementation assumption) */
		so = i = 0;
		while( 1 )  	/* Find the bitmap entry in the root directory (in only first cluster) */
			{
			if( i == 0 )
				{
				if( so >= fs->sectorspercluster )
					return SetErrNo( ENOMEM, -1 );	//FIXME: FR_NO_FILESYSTEM;	/* Not found? */
				if( move_window( fs, clst2sect( fs, (uint32_t)fs->dirbase ) + so ) != FR_OK )
					return SetErrNo( ENOMEM, -1 );	//FIXME: FR_DISK_ERR;
				so++;
				}
			if( fs->win[i] == ET_BITMAP )
				break;			/* Is it a bitmap entry? */
			i = ( i + SIZE_OF_DIR_ENTRY ) % fs->bytespersector;	/* Next entry */
			}
		bcl = ld_dword( fs->win + i + 20 );				/* Bitmap cluster */
		if( bcl < FIRST_VALID_CLUSTER || bcl >= fs->fatentriescount )
			return SetErrNo( ENOMEM, -1 );	//FIXME: FR_NO_FILESYSTEM;	/* (Wrong cluster#) */
		fs->bitbase = fs->database + fs->sectorspercluster * ( bcl - FIRST_VALID_CLUSTER );	/* Bitmap sector */
		while( 1 )  	/* Check if bitmap is contiguous */
			{
			uint32_t cv;

			if( move_window( fs, fs->fatbase + bcl / ( fs->bytespersector / 4 )) != FR_OK )
				return SetErrNo( ENOMEM, -1 );	//FIXME: FR_DISK_ERR;
			cv = ld_dword( fs->win + bcl % ( fs->bytespersector / 4 ) * 4 );
			if( cv == 0xFFFFFFFF )
				break;				/* Last link? */
			if( cv != ++bcl )
				return SetErrNo( ENOMEM, -1 );	//FIXME: FR_NO_FILESYSTEM;	/* Fragmented bitmap? */
			}

		fs->last_clst = fs->freeclusterscount = 0xFFFFFFFF;		/* Initialize cluster allocation information */
		fmt = FFS_FILESYSTEMTYPE_EXFAT;			/* FAT sub-type */
		}
	else
		{
		if( ld_word( fs->win + BPB_BytsPerSec ) != fs->bytespersector )
			return SetErrNo( ENOMEM, -1 );	//FIXME: FR_NO_FILESYSTEM;	/* (BPB_BytsPerSec must be equal to the physical sector size) */

		fasize = ld_word( fs->win + BPB_FATSz16 );		/* Number of sectors per FAT */
		if( fasize == 0 )
			fasize = ld_dword( fs->win + BPB_FATSz32 );
		fs->sectorsperfat = fasize;

		fs->fatcopies = fs->win[BPB_NumFATs];				/* Number of FATs */
		if( fs->fatcopies != 1 && fs->fatcopies != 2 )
			return SetErrNo( ENOMEM, -1 );	//FIXME: FR_NO_FILESYSTEM;	/* (Must be 1 or 2) */
		fasize *= fs->fatcopies;							/* Number of sectors for FAT area */

		fs->sectorspercluster = fs->win[BPB_SecPerClus];			/* Cluster size */
		if( fs->sectorspercluster == 0 || ( fs->sectorspercluster & ( fs->sectorspercluster - 1 )))
			return SetErrNo( ENOMEM, -1 );	//FIXME: FR_NO_FILESYSTEM;	/* (Must be power of 2) */

		fs->rootdirentriescount = ld_word( fs->win + BPB_RootEntCnt );	/* Number of root directory entries */
		if( fs->rootdirentriescount % ( fs->bytespersector / SIZE_OF_DIR_ENTRY ))
			return SetErrNo( ENOMEM, -1 );	//FIXME: FR_NO_FILESYSTEM;	/* (Must be sector aligned) */

		tsect = ld_word( fs->win + BPB_TotSec16 );		/* Number of sectors on the volume */
		if( tsect == 0 )
			tsect = ld_dword( fs->win + BPB_TotSec32 );

		nrsv = ld_word( fs->win + BPB_RsvdSecCnt );		/* Number of reserved sectors */
		if( nrsv == 0 )
			return SetErrNo( ENOMEM, -1 );	//FIXME: FR_NO_FILESYSTEM;			/* (Must not be 0) */

		/* Determine the FAT sub type */
		sysect = nrsv + fasize + fs->rootdirentriescount / ( fs->bytespersector / SIZE_OF_DIR_ENTRY );	/* RSV + FAT + DIR */
		if( tsect < sysect )
			return SetErrNo( ENOMEM, -1 );	//FIXME: FR_NO_FILESYSTEM;	/* (Invalid volume size) */
		nclst = ( tsect - sysect ) / fs->sectorspercluster;			/* Number of clusters */
		if( nclst == 0 )
			return SetErrNo( ENOMEM, -1 );	//FIXME: FR_NO_FILESYSTEM;		/* (Invalid volume size) */
		fmt = 0;
		if( nclst <= MAX_FAT32 )
			fmt = FFS_FILESYSTEMTYPE_FAT32;
		if( nclst <= MAX_FAT16 )
			fmt = FFS_FILESYSTEMTYPE_FAT16;
		if( nclst <= MAX_FAT12 )
			fmt = FFS_FILESYSTEMTYPE_FAT12;
		if( fmt == 0 )
			return SetErrNo( ENOMEM, -1 );	//FIXME: FR_NO_FILESYSTEM;

		/* Boundaries and Limits */
		fs->fatentriescount = nclst + FIRST_VALID_CLUSTER;		/* Number of FAT entries */
		fs->volbase = bsect;							/* Volume start sector */
		fs->fatbase = bsect + nrsv; 					/* FAT start sector */
		fs->database = bsect + sysect;					/* Data start sector */
		if( fmt == FFS_FILESYSTEMTYPE_FAT32 )
			{
			if( ld_word( fs->win + BPB_FSVer32 ) != 0 )
				return SetErrNo( ENOMEM, -1 );	//FIXME: FR_NO_FILESYSTEM;	/* (Must be FAT32 revision 0.0) */
			if( fs->rootdirentriescount != 0 )
				return SetErrNo( ENOMEM, -1 );	//FIXME: FR_NO_FILESYSTEM;	/* (BPB_RootEntCnt must be 0) */
			fs->dirbase = ld_dword( fs->win + BPB_RootClus32 );	/* Root directory start cluster */
			szbfat = fs->fatentriescount * 4;					/* (Needed FAT size) */
			}
		else
			{
			if( fs->rootdirentriescount == 0 )
				return SetErrNo( ENOMEM, -1 );	//FIXME: FR_NO_FILESYSTEM;	/* (BPB_RootEntCnt must not be 0) */
			fs->dirbase = fs->fatbase + fasize;			/* Root directory start sector */
			szbfat = ( fmt == FFS_FILESYSTEMTYPE_FAT16 ) ?				/* (Needed FAT size) */
					 fs->fatentriescount * 2 : fs->fatentriescount * 3 / 2 + ( fs->fatentriescount & 1 );
			}
		if( fs->sectorsperfat < ( szbfat + ( fs->bytespersector - 1 )) / fs->bytespersector )
			return SetErrNo( ENOMEM, -1 );	//FIXME: FR_NO_FILESYSTEM;	/* (BPB_FATSz must not be less than the size needed) */

		/* Get FSInfo if available */
		fs->last_clst = fs->freeclusterscount = 0xFFFFFFFF;		/* Initialize cluster allocation information */
		fs->fsi_flag = 0x80;
#if (FFS_CONFIG_NOFSINFO & 3) != 3
		if( fmt == FFS_FILESYSTEMTYPE_FAT32				/* Allow to update FSInfo only if BPB_FSInfo32 == 1 */
				&& ld_word( fs->win + BPB_FSInfo32 ) == 1
				&& move_window( fs, bsect + 1 ) == FR_OK )
			{
			fs->fsi_flag = 0;
			if( ld_word( fs->win + BS_55AA ) == 0xAA55	/* Load FSInfo data if available */
					&& ld_dword( fs->win + FSI_LeadSig ) == 0x41615252
					&& ld_dword( fs->win + FSI_StrucSig ) == 0x61417272 )
				{
#if (FFS_CONFIG_NOFSINFO & 1) == 0
				fs->freeclusterscount = ld_dword( fs->win + FSI_Free_Count );
#endif
#if (FFS_CONFIG_NOFSINFO & 2) == 0
				fs->last_clst = ld_dword( fs->win + FSI_Nxt_Free );
#endif
				}
			}
#endif	/* (FFS_CONFIG_NOFSINFO & 3) != 3 */
		}

	fs->fs_type	= (uint8_t)fmt; /* FAT sub-type (the filesystem object gets valid) */

	/* Increment 'FFS_FsId' skipping the zero. */
	(void)( ++FFS_FsId || ++FFS_FsId );

	fs->mountid				= FFS_FsId;		/* Volume mount ID */
	fs->curdirstartcluster	= 0;			/* Initialize current directory */

	return 0;
	}
/*============================================================================*/
static ffs_volume_t *FindVolumeFromName( const ffs_char_t *volname )
	{
	int	i;

	/* A NULL or empty volume name indicates the current volume... */
	if( volname == NULL || *volname == '\0' )
		{
		/* ...but there is no current volume... */
		if( !VolumeIndexIsValid( FFS_CurrentVolumeIdx ))
			return NULL;
		/* Return the address of the current volume. */
		return &FFS_Volumes[FFS_CurrentVolumeIdx];
		}

	for( i = 0; VolumeIndexIsValid( i ); i++ )
        {
        char    vn[sizeof FFS_Volumes[i].volname+1];

        strlcpy( vn, FFS_Volumes[i].volname, sizeof vn );
        //strupr( vn );
		char *p;
        for( p = vn; *p != '\0'; p++ )
            *p  = toupper( *p );

		if( strcmp( volname, FFS_Volumes[i].volname ) == 0 )
			return &FFS_Volumes[i];
        }

	// The volume identifier is valid but does not identify any mounted volume.
	return NULL;
	}
/*============================================================================*/
static int FindVolumeIndexFromName( const ffs_char_t *volname )
	{
	int	i;

	/* A NULL or empty volume name indicates the current volume... */
	if( volname == NULL || *volname == '\0' )
		{
		/* ...but there is no current volume... */
		if( !VolumeIndexIsValid( FFS_CurrentVolumeIdx ))
			return INVALID_VOLUME_INDEX;
		/* Return the index of the current volume. */
		return FFS_CurrentVolumeIdx;
		}

	for( i = 0; VolumeIndexIsValid( i ); i++ )
        {
        char    vn[sizeof FFS_Volumes[i].volname+1];

        strlcpy( vn, FFS_Volumes[i].volname, sizeof vn );
        //strupr( vn );
		char *p;
        for( p = vn; *p != '\0'; p++ )
            *p  = toupper( *p );

		if( strcmp( volname, FFS_Volumes[i].volname ) == 0 )
			return i;
        }

	// The volume identifier is valid but does not identify any mounted volume.
	return INVALID_VOLUME_INDEX;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Check if the file/directory object is valid or not                    */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static int ValidateObject( ffs_object_t *obj )
	{
	return obj != NULL &&
		   obj->fs != NULL &&
		   obj->fs->fs_type != FFS_FILESYSTEMTYPE_INVALID &&
		   obj->fsmountid == obj->fs->mountid;
	}
/*============================================================================*/
static int FindVolumeFromPhysicalDriveAndPartition( int physicaldrive, int partition )
	{
	int	i;

	for( i = 0; VolumeIndexIsValid( i ); i++ )
		if( FFS_Volumes[i].fs_type != FFS_FILESYSTEMTYPE_INVALID
		&& FFS_Volumes[i].pdrv == physicaldrive
		&& ( FFS_Volumes[i].partition == partition || partition < 0 ))
			return i;

	return INVALID_VOLUME_INDEX;
	}
/*============================================================================*/
/*  ---------------------------------------------------------------------------

    Public Functions (FatFs API)

    ----------------------------------------------------------------------------*/
/*============================================================================*/
static int _close_a( int fd, int salt );
/*============================================================================*/
static int _umount_a( const ffs_char_t *ldn, int forced )
	{
	ffs_volume_t	*fs;
	int				volidx, fileidx;

	if( !VolumeIndexIsValid( volidx = FindVolumeIndexFromName( ldn )))
		return SetErrNo( EINVAL, -1 ); //FIXME

	fs	= &FFS_Volumes[volidx];

	for( fileidx = 0; FileIndexIsValid( fileidx ); fileidx++ )
		/* We have found an open file in this file system... */
		if( FFS_Files[fileidx].obj != NULL && FFS_Files[fileidx].obj->fs == fs )
			{
			/* ...but we are not forcibly unmounting the file system... */
			if( !forced )
				/* ...so we must stop and return with an error code. */
				return SetErrNo( EINVAL, -1 ); //FIXME
			/* ...and we are forcibly unmounting the file system... */
			else
				/* ...so let's close the file. */
				/* There is no point in testing the status, we cannot take any remediation. */
				_close_a( fileidx, FFS_Files[fileidx].salt );
			}

	ReturnFreeVolumeIdx( volidx );

	/* We just unmounted the current volume... */
	if( FFS_CurrentVolumeIdx == volidx )
		/* ...we must invalidate the current volume. */
		FFS_CurrentVolumeIdx	= INVALID_VOLUME_INDEX;

	return 0;
	}
/*============================================================================*/
int umount( const ffs_char_t *ldname, int forced )
	{
	ffs_char_t	ldn[6];
	int			result;

	/* The system mutex is NULL, meaning that the file system was not initialized,
	   so there are no logical drives mounted and there is nothing we can do here... */
	if( FFS_SystemMutex == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	if( ldname == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	/* This operation is safe to do before acquiring the mutex because it only
	   uses local data.*/
	if( ParseLogicalDriveName( &ldname, ldn, sizeof ldn ) <= 0 )
		return SetErrNo( ENODEV, -1 ); //FIXME

	/* There are extra characters after the logical volume name... */
	if( *ldname != '\0' )
		return SetErrNo( ENODEV, -1 ); //FIXME

	if( !FFS_TakeMutex( FFS_SystemMutex ))
		return SetErrNo( EBUSY, -1 );

	result	= _umount_a( ldn, forced );
	
	FFS_ReleaseMutex( FFS_SystemMutex );

	return result;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Initialize the system                                                      */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static int Initialize( void )
	{
    DeclareIntState( s );
	int	i;

	/* At this point we don't know whether the system was already initialized or
	   not, so we may not have a mutex yet. The test for existence, creation and
	   taking of the mutex must be an atomic  operation. That's why we resort to
	   a critical section. */

	/* Start a critical section, so other tasks cannot interrupt us in the middle
	   of the initialization. */
	s	= FFS_EnterCriticalSection();
	
	/* The mutex already exists, so the system was already initialized... */
	if( FFS_SystemMutex != NULL )
		{
		/* ...we must exit the critical section... */
		FFS_ExitCriticalSection( s );
		/* ...and return a success status. */
		return 1;
		}

	/* Create the mutex. If the creation failed... */
	if(( FFS_SystemMutex = FFS_CreateMutex() ) == NULL )
		{
		/* ...we must exit the critical section... */
		FFS_ExitCriticalSection( s );
		/* ...and return a failure status. */
		return -1;
		}

	/* Now we take the mutex, so other tasks will be blocked from taking it
	   and we can end the critical section. Normally this take operation
	   cannot fail, but if it fails we cannot continue... */
	if( !FFS_TakeMutex( FFS_SystemMutex ))
		{
		/* ...we must delete the mutex so there is no memory leakage... */
		FFS_DeleteMutex( FFS_SystemMutex );
		/* ...and set the mutex pointer to NULL, or else "VERY BAD THINGS"(TM)
		   will happen when some task tries to use the invalid pointer. */
		FFS_SystemMutex	= NULL;
		/* Of course we must end the critical section, otherwise all the task
		   scheduling and some interrupts will stop. In other words, the system
		   will hang. */
		FFS_ExitCriticalSection( s );
		/* Return a failure status. */
		return -1;
		}

	/* Now we can safely end the critical section, because we have already created
	   a mutex and taken it, so the other tasks trying to execute this code will
	   see that the mutex pointer is not NULL and will skip the rest of the
	   initialization. When they try to take the mutex, they will be suspended
	   until we give the mutex back at the end of this initialization. */
	FFS_ExitCriticalSection( s );

	FFS_FreeVolumesIdx	= INVALID_VOLUME_INDEX;
	for( i = 0; VolumeIndexIsValid( i ); i++ )
		ReturnFreeVolumeIdx( i );

	FFS_FreeObjectsIdx	= INVALID_OBJECT_INDEX;
	for( i = 0; ObjectIndexIsValid( i ); i++ )
		ReturnFreeObjectIdx( i );

	FFS_FreeFilesIdx	= INVALID_FILE_INDEX;
	for( i = 0; FileIndexIsValid( i ); i++ )
		ReturnFreeFileIdx( i );

	/* Release the mutex, so other tasks can resume file system operations. */
	FFS_ReleaseMutex( FFS_SystemMutex );

	/* Return a succes status. */
	return 0;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Mount/Unmount a Logical Drive                                         */
/*----------------------------------------------------------------------------*/
/*============================================================================*/

/***************************************************************************//**
Mounts a partition in a physical drive to a logical volume. This function is to be called only
internally, after acquiring the system mutex.
@param[in]  pdn			A string with the name of the physical drive name to mount.
@param[in]  partition	The number of the partition in the physical drive to mount, or -1 if no particular partition is specified
@param[in]  ldn			A string with the name of the logical drive name to receive the mount.
@returns				On failure returns -1 and sets 'errno', on success return 0 (zero) and doesn't touch 'errno'.
*******************************************************************************/

static int _mount_a( const ffs_char_t *pdn, int partition, const ffs_char_t *ldn )
	{
	ffs_volume_t	*fs;
	int				pdrvidx, volidx;

	pdrvidx	= FFS_FindPhysicalDriveByName( pdn );
	if( pdrvidx < 0 )
		return SetErrNo( ENODEV, -1 );	//FIXED

	// This Drive/Partition combination is already mounted...
	if( VolumeIndexIsValid( FindVolumeFromPhysicalDriveAndPartition( pdrvidx, partition )))
		//...return a failure status.
		return SetErrNo( EBUSY, -1 );	//FIXED

	if( !VolumeIndexIsValid( volidx	= GetFreeVolumeIdx() ))
		return SetErrNo( ENOMEM, -1 );	//FIXED

	fs	= &FFS_Volumes[volidx];

	fs->pdrv		= pdrvidx;
	fs->ldrv		= volidx;
	fs->partition	= partition >= 0 ? partition : 0;
	fs->fs_type		= FFS_FILESYSTEMTYPE_INVALID;			/* Clear new fs object */
	strlcpy( fs->volname, ldn, sizeof fs->volname );

	if( mount_volume( fs, _FREAD ) != 0 )				/* Force mounted the volume */
		{
		ReturnFreeVolumeIdx( volidx );
		return -1;
		}

	if( !VolumeIndexIsValid( FFS_CurrentVolumeIdx ))
		FFS_CurrentVolumeIdx	= volidx;

	return 0;
	}
/*----------------------------------------------------------------------------*/

/***************************************************************************//**
Mounts a partition in a physical drive to a logical volume.
@param[in]  pdname	A string with the name of the physical drive name to mount.
@param[in]  ldname	A string with the name of the logical drive name to receive the mount.
@returns			On failure returns -1 and sets 'errno', on success return 0 (zero) and doesn't touch 'errno'.
*******************************************************************************/

int mount( const ffs_char_t *pdname, const ffs_char_t *ldname )
	{
	ffs_char_t	pdn[6], ldn[6];
	int			partition;
	int			result;

	/* We are testing the existence of the mutex here to save time in the case
	   the system was alread initialized. It is tested again inside a critical
	   section in function Initialize() to ensure data integrity. */
	if( FFS_SystemMutex == NULL )
		Initialize();

	if( pdname == NULL || ldname == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXED

	/* This operation is safe to do before acquiring the mutex because it only
	   uses local data.*/
	if( ParsePhysicalDriveName( pdname, pdn, sizeof pdn, &partition ) < 0 )
		return SetErrNo( EINVAL, -1 ); //FIXED

	/* This operation is safe to do before acquiring the mutex because it only
	   uses local data.*/
	if( ParseLogicalDriveName( &ldname, ldn, sizeof ldn ) <= 0 )
		return SetErrNo( EINVAL, -1 ); //FIXED

	/* There are extra characters after the logical volume name... */
	if( *ldname != '\0' )
		return SetErrNo( EINVAL, -1 ); //FIXED

	if( !FFS_TakeMutex( FFS_SystemMutex ))
		return SetErrNo( EBUSY, -1 ); //FIXED
	
	result	= _mount_a( pdn, partition, ldn );
	
	FFS_ReleaseMutex( FFS_SystemMutex );

	return result;
	}
/*============================================================================*/
static int TruncateFile( ffs_volume_t *fs, ffs_directory_t *dj, ffs_file_t *fp )
	{
	uint32_t	tm;

	tm	= get_fattime();					/* Set created time */

	if( fs->fs_type == FFS_FILESYSTEMTYPE_EXFAT )
		{
		ffs_object_t *obj;

		/* Get current allocation info */
		obj		= fp->obj;
		obj->fs	= fs;
		init_alloc_info( fs, obj );
		/* Set directory entry block initial state */
		memset( fs->dirbuf + 2, 0, 30 );							/* Clear 85 entry except for NumSec */
		memset( fs->dirbuf + 38, 0, 26 );							/* Clear C0 entry except for NumName and NameHash */
		fs->dirbuf[XDIR_Attr]	= FFS_FILEATTRIBUTE_ARCHIVE;
		//st_dword( fs->dirbuf + XDIR_CrtTime, tm );
		st_dword( fs->dirbuf + XDIR_ModTime, tm );
		fs->dirbuf[XDIR_GenFlags] = 1;

		if( store_xdir( dj ) != FR_OK )
			return SetErrNo( EINVAL, -1 );	//FIXME

		if( obj->startcluster != 0 )	/* Remove the cluster chain if exist */
			{
			if( remove_chain( obj, obj->startcluster, 0 ) != FR_OK )
				{
				fs->last_clst = obj->startcluster - 1;		/* Reuse the cluster hole */
				return SetErrNo( EINVAL, -1 );	//FIXME
				}
			fs->last_clst = obj->startcluster - 1;		/* Reuse the cluster hole */
			}
		}
	else
		{
		uint32_t	cl;

		/* Set directory entry initial state */
		//st_dword( dj->dir + DIR_CrtTime, tm );
		st_dword( dj->dir + DIR_ModTime, tm );
		cl = ld_clust( fs, dj->dir );			/* Get current cluster chain */
		dj->dir[DIR_Attr] = FFS_FILEATTRIBUTE_ARCHIVE;			/* Reset attribute */
		st_clust( fs, dj->dir, 0 );			/* Reset file allocation info */
		st_dword( dj->dir + DIR_FileSize, 0 );
		fs->windowisdirty = 1;
		if( cl != 0 )  						/* Remove the cluster chain if exist */
			{
			lba_t	sc;

			sc = fs->sectorinwin;
			if( remove_chain( dj->obj, cl, 0 ) != FR_OK )
				return SetErrNo( EINVAL, -1 );	//FIXME

			if( move_window( fs, sc ) != FR_OK )
				{
				fs->last_clst = cl - 1;			/* Reuse the cluster hole */
				return SetErrNo( EINVAL, -1 );	//FIXME
				}

			fs->last_clst = cl - 1;				/* Reuse the cluster hole */
			}
		}

	return 0;
	}
/*============================================================================*/
static int SeekEnd( ffs_volume_t *fs, ffs_file_t *fp )
	{
	filesize_t		ofs;
	ffs_object_t	*obj;
	uint32_t		bcs, clst;

	obj	= fp->obj;

	if( obj->sizebytes == 0x18d6 )
		{
		asm volatile( "nop" );
		asm volatile( "nop" );
		asm volatile( "nop" );
		}

	if( obj->sizebytes == 0 )  						/* Seek to end of file if _FAPPEND is specified */
		{
		fp->fptr			= 0;
		fp->currentcluster	= obj->startcluster;
		return 0;
		}

	fp->fptr	= obj->sizebytes;					/* Offset to seek */
	bcs			= (uint32_t)fs->sectorspercluster * fs->bytespersector;	/* Cluster size in byte */
	clst		= obj->startcluster;					/* Follow the cluster chain */

	for( ofs = obj->sizebytes; ofs > bcs; ofs -= bcs )
		{
		clst = get_fat( obj, clst );

		if( clst < FIRST_VALID_CLUSTER )
			return SetErrNo( EINVAL, -1 );	//FIXME: FR_INT_ERR
		else if( clst == 0xFFFFFFFF )
			return SetErrNo( EINVAL, -1 );	//FIXME: FR_DISK_ERR
		}

	fp->currentcluster	= clst;

	return 0;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Open or Create a File                                                      */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
#define	MODECOUNTERS_INDEX_APPEND	0
#define	MODECOUNTERS_INDEX_WRITE	1
/*----------------------------------------------------------------------------*/
#define	M_NOTOPEN	0
#define	M_READ		1
#define M_APPEND	2
#define	M_WRITE		3
/*----------------------------------------------------------------------------*/
/* These flags are to be used for parameter 'pmode' to allow for file-sharing
among tasks. They mean: 'Deny Access to Others', 'Allow Others to Read',
'Allow Others to Append' and 'Allow Others to Write'. If the flags are not
compatible with the ones from a previous call to 'open', the call will fail. */
/*----------------------------------------------------------------------------*/
#define	ACCESSCOUNTERS_INDEX_READ	0
#define	ACCESSCOUNTERS_INDEX_APPEND	1
/*----------------------------------------------------------------------------*/
#define	A_EXCLUSIVE	0
#define	A_READ		1
#define	A_APPEND	2
#define	A_WRITE		3
/*----------------------------------------------------------------------------*/
/* Pointer to the blank file object */
/* Pointer to the file name */
/* Access mode and open mode flags */

static int _open_b( ffs_volume_t *fs, ffs_file_t *fp, const ffs_char_t *path, int oflag, int pmode )
	{
	ffs_directory_t	dj;
	ffs_object_t	djobj;
	ffs_object_t	*fpobj;
	int				fpobjidx	= INVALID_OBJECT_INDEX;
	int				newobject	=  0;
	ffs_fresult_t	res;
	uint8_t			access, mode;

	dj.obj			= &djobj;
	dj.objectidx	= INVALID_OBJECT_INDEX;
	dj.obj->fs		= fs;

	fpobj	= fp->obj;
//@@@	fpobj->statusflags	= 0;

	mode	= ( oflag & _FWRITE ) ? (( oflag & _FAPPEND ) ? M_APPEND : M_WRITE ) : M_READ;
	access	= (uint8_t)pmode;

	res		= follow_path( &dj, path );	/* Follow the file path */
	
	/* The file exists... */
	if( res == FR_OK )
		{
		/* It seems that the file is the "Origin" directory... */
		if( dj.fn[NSFLAG] & NS_NONAME )
			/* ...we return the same status as if the file did not exist. */
			return SetErrNo( ENOENT, -1 );

		/* We cannot open a directory at all or a read-only file for writing... */
		if(( dj.obj->attributes & FFS_FILEATTRIBUTE_DIRECTORY ) || (( oflag & _FWRITE ) && ( dj.obj->attributes & FFS_FILEATTRIBUTE_READONLY )))
			return SetErrNo( EACCES, -1 );	//@@@@FIXME: errno value

		/* The file is to be created exclusive... */
		if(( oflag & _FCREAT ) && ( oflag & _FEXCL ))
			/* ...so we cannot re-create it now. */
			return SetErrNo( EEXIST, -1 );

		/* We found a matching object, the file is already open... */
		if( ObjectIndexIsValid( fpobjidx = SearchObject( &dj )))
			{
			/* ...we will (and must) re-use the same object. */
			fpobj	= &FFS_Objects[fpobjidx];

			/* The flags don't allow concurrent access... */
			/* ...either we are requesting an open mode that is above the
			   current access mode or we are requesting an access mode that
			   is stricter than the current open mode... */
			if( mode > fpobj->access || fpobj->mode > access )
				/* ...we cannot open the file again. */
				return SetErrNo( ENOENT, -1 );

			/* We must truncate the file to zero size... */
			if( oflag & _FTRUNC )
				{
				/* To truncate the file, we must have write access to it, even
				   if our open mode is for append... */
				if( fpobj->access != A_WRITE )
					return SetErrNo( ENOENT, -1 );	//@@@@FIXME: errno value
					
				/* ...let's try... */
				if( TruncateFile( fs, &dj, fp ) != FR_OK )
					return SetErrNo( ENOENT, -1 );	//@@@@FIXME: errno value
				/* We just trucated the file, so it is modified now, signal it. */
				fpobj->statusflags	= FA_MODIFIED;
				}
			}
		/* The file is not open... */
		else
			{
			/* We will need a free object... */
			if( !ObjectIndexIsValid( fpobjidx = GetFreeObjectIdx() ))
				/* ...but there is none free. */
				return SetErrNo( ENOENT, -1 );	//@@@@FIXME: errno value

			/* Let's use this newly acquired object. */
			fpobj		= &FFS_Objects[fpobjidx];
			newobject	= 1;

			/* We must truncate the file to zero size... */
			if( oflag & _FTRUNC )	//FIXME: Check Read-Only
				{
				/* ...let's try... */
				if( TruncateFile( fs, &dj, fp ) != FR_OK )
					{
					/* ...but it failed. We must return the object we just acquired... */
					ReturnFreeObjectIdx( fpobjidx );
					/* ...and return a failure status. */
					return SetErrNo( ENOENT, -1 );	//@@@@FIXME: errno value
					}
				/* We just truncated the file, so it is modified now, signal it. */
				fpobj->statusflags	= FA_MODIFIED;
				}
			}
		}
	/* The file doesn't exist... */
	else if( res == FR_NO_FILE )
		{
		/* ...and it is not to be created... */
		if( !( oflag & _FCREAT ))
			/* ...nothing to do. */
			return SetErrNo( ENOENT, -1 );	//@@@@FIXME: errno value
		/* We will need a free object to use... */
		if( !ObjectIndexIsValid( fpobjidx = GetFreeObjectIdx() ))
			/* ...but there is no more free objects available. */
			return SetErrNo( ENOENT, -1 );
		/* Let's use this newly acquired object. */
		fpobj		= &FFS_Objects[fpobjidx];
		newobject	= 1;
		/* The file is new, we must register it in its directory. */
		dir_register( &dj );
		/* Creating a file is, in a sense, modifying it, signal it. */
		fpobj->statusflags	= FA_MODIFIED;
		}
	/* Some other error happened... */
	else
		return SetErrNo( ENOENT, -1 );	//@@@@FIXME: errno value

	fp->obj			= fpobj;
	fp->objectidx	= fpobjidx;

	if( newobject )
		{
		uint32_t	tm;

		fpobj->refcounter								= 0;
		fpobj->mode										= M_NOTOPEN;
		fpobj->modecntrs[MODECOUNTERS_INDEX_APPEND]		= 0;
		fpobj->modecntrs[MODECOUNTERS_INDEX_WRITE]		= 0;
		fpobj->access									= A_WRITE;
		fpobj->acccntrs[ACCESSCOUNTERS_INDEX_READ]		= 0;
		fpobj->acccntrs[ACCESSCOUNTERS_INDEX_APPEND]	= 0;

		fpobj->sectorofdirentry			= fs->sectorinwin;						/* Pointer to the directory entry */
		fpobj->ptrtodirentry			= dj.dir;
		fpobj->fs						= fs;								/* Validate the file object */
		fpobj->fsmountid				= fs->mountid;
		fpobj->parentdirstartcluster	= dj.obj->startcluster;					/* Get containing directory info */
		fpobj->parentdirsize			= ( (uint32_t)dj.obj->sizebytes & 0xFFFFFF00 ) | dj.obj->chainstatus;
		fpobj->sectorinbuf				= 0;								/* Invalidate current data sector */
		fpobj->statusflags				= 0;

		tm	= get_fattime();					/* Set created time */

		if( fs->fs_type == FFS_FILESYSTEMTYPE_EXFAT )
			{
			fpobj->offsetinparentdir		= dj.blk_ofs;
			init_alloc_info( fs, fpobj );
			st_dword( fs->dirbuf + XDIR_CrtTime, tm );
			st_dword( fs->dirbuf + XDIR_ModTime, tm );				/* Created time */
			}
		else
			{
			fpobj->offsetinparentdir		= dj.direntryoffset;
			fpobj->startcluster		= ld_clust( fs, dj.dir );					/* Get object allocation info */
			fpobj->sizebytes	= ld_dword( dj.dir + DIR_FileSize );
			st_dword( dj.dir + DIR_CrtTime, tm );
			st_dword( dj.dir + DIR_ModTime, tm );	/* Created time */
			}

#if 0
		/* The original version does not do this, so the directory entry is not
		   created on disk until the file is closed or some other write operation
		   forces the window to be synch-ed. */
		if( sync_window( fs ) != FR_OK )
			{
			ReturnFreeObjectIdx( fpobjidx );
			return SetErrNo( EINVAL, -1 ); //FIXME
			}
#endif
		}

	if( access < fpobj->access )
		fpobj->access = access;
	if( access >= A_READ && access <= A_APPEND )
		fpobj->acccntrs[access - A_READ]++;

	if( mode > fpobj->mode )
		fpobj->mode	= mode;
	if( mode >= M_APPEND && mode <= M_WRITE )
		fpobj->modecntrs[mode-M_APPEND]++;

	/* Increment the number of references to the object. */
	fpobj->refcounter++;

	fp->mode			= oflag;							/* Set file access Flags */
	fp->err				= 0;								/* Clear error flag */
	fp->fptr			= 0;								/* Set file pointer top of the file */

//	memset( fpobj->buf, 0, sizeof fpobj->buf );				/* Clear sector buffer */
//	fpobj->sectorinbuf	= 0;								/* Invalidate current data sector */

	return 0;
	}
/*----------------------------------------------------------------------------*/
/* This is the number of pre-opened streams available for C programs: stdin, 
stdout and stder. We do not deal with them for now. Later we will need to
redirect transactions to them to other functions. */
/*----------------------------------------------------------------------------*/
#define	FFS_RESERVED_FILES	3
/*----------------------------------------------------------------------------*/
static int _open_a( const ffs_char_t *ldn, const ffs_char_t *path, int oflag, int pmode )
	{
	ffs_volume_t	*fs;
	ffs_file_t		*fp;
	int				fileidx;

	if(( fs = FindVolumeFromName( ldn )) == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	if( !FileIndexIsValid( fileidx = GetFreeFileIdx() ))
		return SetErrNo( ENFILE, -1 );

	fp	= &FFS_Files[fileidx];

	if( _open_b( fs, fp, path, oflag, pmode ) == -1 )
		{
		ReturnFreeFileIdx( fileidx );
		return SetErrNo( EINVAL, -1 ); //FIXME
		}

	/* Increment 'FFS_CurrentSalt' skipping the zero. */
	if(( FFS_CurrentSalt = ( FFS_CurrentSalt + 1 ) & (( 1 << 7 ) - 1 )) == 0 )
		FFS_CurrentSalt++;

	fp->salt	= FFS_CurrentSalt;

	return ( fileidx + FFS_RESERVED_FILES ) | ( fp->salt << 8 );
	}
/*----------------------------------------------------------------------------*/
int _open( const ffs_char_t *path, int oflag, mode_t pmode )
	{
	ffs_char_t	ldn[6];
	int			result;

	/* The system mutex is NULL, meaning that the file system was not initialized,
	   so there are no logical drives mounted and there is nothing we can do here... */
	if( FFS_SystemMutex == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	if( path == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	/* We are using a trick here, since 'pmode' is an optional parameter to 'open',
	   and we have no way of knowing if it was provided or not, all valid values must
	   have a fixed part like a 'magic number'. */

	/* We did not find the 'magic number', the parameter 'pmode' was not supplied...  */
	if(( pmode & 0xfffffffc ) != 0x15aacba0 )
		/* ...so we use the default value. */
		pmode	= A_READ;
	/* Remove the magic number. Valid values range from A_READ to A_WRITE (0 to 3)... */
	if(( pmode &= 0x00000003 ) > A_WRITE )
		return SetErrNo( EINVAL, -1 );

	/* Valid values for the three lower bits range from O_RDONLY to O_RDWR (0 to 2)... */
	if(( oflag & 0x00000007 ) > O_RDWR )
		return SetErrNo( EINVAL, -1 );
	/* Transform the values O_RDONLY, O_WRONLY and O_RDWR into bits _FREAD and _FWRITE. */
	oflag++;
#if			defined FFS_CONFIG_PERMISSIVE
	/* The flag _FAPPEND implies write access... */
	if( oflag & _FAPPEND )
		oflag  |= _FWRITE;
#endif	/*	defined FFS_CONFIG_PERMISSIVE */
	/* _FAPPEND, _FTRUNC and _FCREAT can only be used with _FWRITE, _FEXCL can only be used with _FCREAT. */
	if((( oflag & ( _FAPPEND | _FTRUNC | _FCREAT )) && !( oflag & _FWRITE )) || (( oflag & _FEXCL ) && !( oflag & _FCREAT )))
		return SetErrNo( EINVAL, -1 );

	/* This operation is safe to do before acquiring the mutex because it only
	   uses local data. We could do it inside '_open_a', but then we would have
	   more wasted time with mutex acquired in case of error. */
	if( ParseLogicalDriveName( &path, ldn, sizeof ldn ) < 0 )
		return SetErrNo( ENODEV, -1 ); //FIXME

	/* From now on we must have exclusive access to the file system... */
	if( !FFS_TakeMutex( FFS_SystemMutex ))
		/* ...but it was hogged for too long by another task. */
		return SetErrNo( EBUSY, -1 );

	/* Call the function that is going to do the real dirty work. */
	result	= _open_a( ldn, path, oflag, pmode );

	/* Release the mutex, because we don't want to hang the system. */
	FFS_ReleaseMutex( FFS_SystemMutex );

	return result;
	}
/*============================================================================*/
static inline int __attribute__((always_inline)) ClusterIsValid( ffs_volume_t *fs, uint32_t Cluster )
	{
	return Cluster >= FIRST_VALID_CLUSTER && Cluster <= fs->fatentriescount;
	}
/*----------------------------------------------------------------------------*/
static inline int __attribute__((always_inline)) min( int a, int b )
	{
	return a < b ? a : b;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Read File                                                             */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static int _read_a( int fd, int salt, uint8_t *buff, size_t BytesToRead )
	{
	ffs_file_t		*fp;
	ffs_object_t	*obj;
	ffs_volume_t	*fs;
	lba_t			CurrentSector;
	size_t			BytesRead;
	int				SegmentSize;
	int				OffsetInSector;
	int				RemainingSectorsInCluster;

	fp	= &FFS_Files[fd];

	if( salt != fp->salt )
		return SetErrNo( ENFILE, -1 );

	obj	= fp->obj;

	if( !ValidateObject( obj ))
		return SetErrNo( ENFILE, -1 );

	fs	= obj->fs;

	if(( disk_status( fs->pdrv ) & STA_NOINIT ))
		return SetErrNo( ENFILE, -1 );	//FIXME: FR_DISK_ERR

	if( (ffs_fresult_t)fp->err != FR_OK )
		return SetErrNo( ENFILE, -1 );

	if( obj->sizebytes == 0 || fp->fptr >= obj->sizebytes || obj->startcluster == 0 )
		return 0;
	
	if( fp->fptr + BytesToRead > obj->sizebytes )
		BytesToRead	= obj->sizebytes - fp->fptr;

	if( BytesToRead == 0 )
		return 0;

	if( fp->fptr == 0 )
		fp->currentcluster		= obj->startcluster;
	RemainingSectorsInCluster	= (( ~fp->fptr & ( fs->bytespersector * fs->sectorspercluster - 1 )) + fs->bytespersector - 1 ) / fs->bytespersector;
	OffsetInSector				= fp->fptr & ( fs->bytespersector - 1 );
	CurrentSector				= clst2sect( fs, fp->currentcluster ) + fs->sectorspercluster - RemainingSectorsInCluster;

	for( BytesRead = 0; BytesToRead > 0; BytesRead += SegmentSize, BytesToRead -= SegmentSize, buff += SegmentSize )
		{
		int	SectorCount;

		if( RemainingSectorsInCluster == 0 )
			{
			uint32_t	NextCluster;

			/* Either we do not have a valid cluster or it is the last one in the chain... */
			if( !ClusterIsValid( fs, fp->currentcluster ) || !ClusterIsValid( fs, NextCluster = get_fat( obj, fp->currentcluster )))
				return SetErrNo( ENFILE, -1 );

			CurrentSector				= clst2sect( fs, NextCluster );
			RemainingSectorsInCluster	= fs->sectorspercluster;
			fp->currentcluster			= NextCluster;
			OffsetInSector				= 0;
			}

		if(( obj->statusflags & FA_DIRTY ) && CurrentSector != obj->sectorinbuf )
			{
			if( obj->sectorinbuf != 0 && disk_write( fs->pdrv, obj->buf, obj->sectorinbuf, 1 ) != RES_OK )
				return SetErrNo( ENFILE, -1 );
			obj->statusflags &= ~FA_DIRTY;
			}

		/* We can read one or more entire sectors at once... */
		if( OffsetInSector == 0 && BytesToRead >= fs->bytespersector )
			{
			/* Calculate the maximum number of sectors that we can read. */
			SectorCount	= min( BytesToRead / fs->bytespersector, RemainingSectorsInCluster );
			SegmentSize	= SectorCount * fs->bytespersector;

			if( disk_read( fs->pdrv, buff, CurrentSector, SectorCount ) != RES_OK )
				return SetErrNo( ENFILE, -1 );
			}
		else
			{
			SectorCount	= 1;
			SegmentSize	= min( BytesToRead, fs->bytespersector - OffsetInSector );

			/* We have a sector already and it is not loaded in the buffer... */
			if( obj->sectorinbuf != CurrentSector )
				{
				/* ...let's load it... */
				disk_read( fs->pdrv, obj->buf, CurrentSector, 1 );
				obj->sectorinbuf	= CurrentSector;
				}

			memcpy( buff, &obj->buf[OffsetInSector], SegmentSize );
			OffsetInSector	   += SegmentSize;
			}

		fp->fptr			   += SegmentSize;
		
		/* The sector is over and it is not the last one in the cluster... */
		if( OffsetInSector >= fs->bytespersector && ( RemainingSectorsInCluster -= SectorCount ) > 0 )
			{
			/* ...let's just use the next one. */
			CurrentSector++;
			OffsetInSector			= 0;
			}
		}

	return BytesRead;
	}
/*----------------------------------------------------------------------------*/
int _read( int fd, void *ptr, size_t len )
	{
	int	result, salt;

	/* The system mutex is NULL, meaning that the file system was not initialized,
	   so there are no logical drives mounted and there is nothing we can do here... */
	if( FFS_SystemMutex == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	salt	= fd >> 8;
	fd 		= ( fd & 0xff ) - FFS_RESERVED_FILES;

	if( !FileIndexIsValid( fd ))
		return SetErrNo( ENFILE, -1 );

	if( !FFS_TakeMutex( FFS_SystemMutex ))
		return SetErrNo( EBUSY, -1 );

	result	= _read_a( fd, salt, ptr, len );

	FFS_ReleaseMutex( FFS_SystemMutex );

	return result;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Write File                                                            */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static int _write_a( int fd, int salt, const uint8_t *buff, size_t BytesToWrite ) /* Pointer to the file object */ /* Pointer to the data to be written */ /* Number of bytes to write */ /* Pointer to number of bytes written */
	{
	ffs_file_t		*fp;
	ffs_object_t	*obj;
	ffs_volume_t	*fs;
	lba_t			CurrentSector;
	size_t			BytesWritten;
	int				SegmentSize;
	int				OffsetInSector;
	int				RemainingSectorsInCluster;
	int				SectorHasData;

	fp	= &FFS_Files[fd];

	if( salt != fp->salt )
		return SetErrNo( ENFILE, -1 );

	obj	= fp->obj;

	if( !ValidateObject( obj ))
		return SetErrNo( ENFILE, -1 );

	fs	= obj->fs;

	if(( disk_status( fs->pdrv ) & STA_NOINIT ))
		return SetErrNo( ENFILE, -1 );	//FIXME: FR_DISK_ERR

	if( (ffs_fresult_t)fp->err != FR_OK )
		return SetErrNo( ENFILE, -1 );

	if( !( fp->mode & _FWRITE ))
		return SetErrNo( ENFILE, -1 );	//FIXME: FR_DENIED

	/* Check fptr wrap-around (file size cannot reach 4 GiB at FAT volume) */
	if(( fs->fs_type != FFS_FILESYSTEMTYPE_EXFAT ) && (uint32_t)( fp->fptr + BytesToWrite ) < (uint32_t)fp->fptr )
		BytesToWrite = (unsigned int)( 0xFFFFFFFF - (uint32_t)fp->fptr );

	if( BytesToWrite == 0 )
		return 0;

	if(( fp->mode & _FAPPEND ) && SeekEnd( fs, fp ) < 0 )
		return SetErrNo( ENFILE, -1 );	//FIXME

	/* The file is empty... */
	if( obj->sizebytes == 0 || !ClusterIsValid( fs, obj->startcluster ))
		{
		fp->fptr					=  0;
		obj->startcluster			=  0;
		obj->sizebytes 				=  0;
		fp->currentcluster			=  0;
		RemainingSectorsInCluster	=  0;
		OffsetInSector				=  0;
		CurrentSector				=  0;
		SectorHasData				=  0;
		}
	else
		{
		if( fp->fptr == 0 )
			fp->currentcluster		= obj->startcluster;
		RemainingSectorsInCluster	= (( -fp->fptr & ( fs->bytespersector * fs->sectorspercluster - 1 )) + fs->bytespersector - 1 ) / fs->bytespersector;
		OffsetInSector				= fp->fptr & ( fs->bytespersector - 1 );
		CurrentSector				= clst2sect( fs, fp->currentcluster ) + fs->sectorspercluster - RemainingSectorsInCluster;
		SectorHasData				= fp->fptr < obj->sizebytes || OffsetInSector > 0;
		}

	for( BytesWritten = 0; BytesToWrite > 0; BytesWritten += SegmentSize, BytesToWrite -= SegmentSize, buff += SegmentSize )
		{
		int	SectorCount;

		if( CurrentSector == 0 || RemainingSectorsInCluster == 0 )
			{
			uint32_t	NextCluster;

			/* Either we do not have a valid cluster or it is the last one in the chain... */
			if( !ClusterIsValid( fs, fp->currentcluster ) || !ClusterIsValid( fs, NextCluster = get_fat( obj, fp->currentcluster )))
				{
				/* ...we will need a new cluster anyway... */
				if( !ClusterIsValid( fs, NextCluster = create_chain( obj, 0 )))
					/* ...but the volume if full. */
					return SetErrNo( ENFILE, -1 );
#if 0	/* Using create_chain() frees us from doing this. */
				/* We must mark the new cluster as used and end of chain...*/
				if( put_fat( fs, NextCluster, 0xFFFFFFFF ) != FR_OK )
					/* ...but some error happened. */
					return SetErrNo( ENFILE, -1 );
#endif
				/* Our current cluster is valid... */
				if( ClusterIsValid( fs, fp->currentcluster ))
					{
					/* ...we must make it point to the next... */
					if( put_fat( fs, fp->currentcluster, NextCluster ) != FR_OK )
						/* ...but some error happened. */
						return SetErrNo( ENFILE, -1 );
					}
				/* Our current cluster is not valid. That means that our file was empty... */
				else
					/* ...we must change the directory and object data. */
					obj->startcluster	= NextCluster;
				}

			CurrentSector				= clst2sect( fs, NextCluster );
			RemainingSectorsInCluster	= fs->sectorspercluster;
			SectorHasData				= fp->fptr < obj->sizebytes;
			fp->currentcluster			= NextCluster;
			OffsetInSector				= 0;
			}

		if(( obj->statusflags & FA_DIRTY ) && CurrentSector != obj->sectorinbuf )
			{
			if( obj->sectorinbuf != 0 && disk_write( fs->pdrv, obj->buf, obj->sectorinbuf, 1 ) != RES_OK )
				return SetErrNo( ENFILE, -1 );
			obj->statusflags &= ~FA_DIRTY;
			}

		/* We can write one or more entire sectors at once... */
		if( OffsetInSector == 0 && BytesToWrite >= fs->bytespersector /*&& RemainingSectorsInCluster >= 1*/ )
			{
			/* Calculate the maximum number of sectors that we can write. */
			SectorCount	= min( BytesToWrite / fs->bytespersector, RemainingSectorsInCluster );
			SegmentSize	= SectorCount * fs->bytespersector;

			if( disk_write( fs->pdrv, buff, CurrentSector, SectorCount ) != RES_OK )
				return SetErrNo( ENFILE, -1 );
			}
		else
			{
			SectorCount	= 1;
			SegmentSize	= min( BytesToWrite, fs->bytespersector - OffsetInSector );

			if( SectorHasData )
				{
				/* We have a sector already and it is not loaded in the buffer... */
				if( obj->sectorinbuf != CurrentSector && SegmentSize < fs->bytespersector )
					/* ...let's load it... */
					disk_read( fs->pdrv, obj->buf, CurrentSector, 1 );
				}
			else if( !( obj->statusflags & FA_DIRTY ))
				/* ...let's just clear the buffer and hope to get a sector later. */
				memset( obj->buf, 0, sizeof obj->buf && SegmentSize < fs->bytespersector );

			obj->sectorinbuf	= CurrentSector;
			memcpy( &obj->buf[OffsetInSector], buff, SegmentSize );
			OffsetInSector	   += SegmentSize;
			obj->statusflags   |= FA_DIRTY;
			}

		fp->fptr			   += SegmentSize;
		if( fp->fptr > obj->sizebytes )
			obj->sizebytes		= fp->fptr;
		
		/* The sector is full and it is not the last one in the cluster... */
		if( OffsetInSector >= fs->bytespersector && ( RemainingSectorsInCluster -= SectorCount ) > 0 )
			{
			/* ...let's just use the next one. */
			CurrentSector++;
			OffsetInSector			= 0;
			SectorHasData			= fp->fptr < obj->sizebytes;
			}
		}

	obj->statusflags |= FA_MODIFIED;

	return BytesWritten;
	}
/*----------------------------------------------------------------------------*/
int _write( int fd, const void *ptr, size_t len )
	{
	int	result, salt;

	/* The system mutex is NULL, meaning that the file system was not initialized,
	   so there are no logical drives mounted and there is nothing we can do here... */
	if( FFS_SystemMutex == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	salt	= fd >> 8;
	fd 		= ( fd & 0xff ) - FFS_RESERVED_FILES;

	if( !FileIndexIsValid( fd ))
		return SetErrNo( ENFILE, -1 );

	if( !FFS_TakeMutex( FFS_SystemMutex ))
		return SetErrNo( EBUSY, -1 );

	result	= _write_a( fd, salt, ptr, len );

	FFS_ReleaseMutex( FFS_SystemMutex );
	
	return result;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Synchronize the File                                                  */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static int _fsync_b( ffs_file_t* fp )		/* Open file to be synced */
	{
	ffs_volume_t	*fs;
	ffs_object_t	*obj;
	uint32_t		tm;

	obj	= fp->obj;

	if( !( obj->statusflags & FA_MODIFIED ))  	/* Is there any change to the file? */
		return 0;

	fs	= obj->fs;

	if( obj->statusflags & FA_DIRTY )  			/* Write-back cached data if needed */
		{
		if( disk_write( fs->pdrv, obj->buf, obj->sectorinbuf, 1 ) != RES_OK )
			return SetErrNo( EINVAL, -1 ); //FIXME: FR_DISK_ERR;

		obj->statusflags &= (uint8_t)~FA_DIRTY;
		}

	/* Update the directory entry */
	tm = get_fattime();				/* Modified time */
	if( fs->fs_type == FFS_FILESYSTEMTYPE_EXFAT )
		{
		ffs_directory_t	dj;
		ffs_object_t	djobj;

		if( fill_first_frag( obj ) != FR_OK )	/* Fill first fragment on the FAT if needed */
			return SetErrNo( EINVAL, -1 ); //FIXME

		if( fill_last_frag( obj, fp->currentcluster, 0xFFFFFFFF ) != FR_OK )	/* Fill last fragment on the FAT if needed */
			return SetErrNo( EINVAL, -1 ); //FIXME

		dj.obj			= &djobj;
		dj.objectidx	= INVALID_OBJECT_INDEX;
		dj.obj->fs		= fs;

		if( load_obj_xdir( &dj, obj ) != FR_OK )	/* Load directory entry block */
			return SetErrNo( EINVAL, -1 ); //FIXME

		fs->dirbuf[XDIR_Attr] |= FFS_FILEATTRIBUTE_ARCHIVE;				/* Set archive attribute to indicate that the file has been changed */
		fs->dirbuf[XDIR_GenFlags] = obj->chainstatus | FFS_CHAINSTATUS_ALLOCATIONPOSSIBLE;	/* Update file allocation information */
		st_dword( fs->dirbuf + XDIR_FstClus, obj->startcluster );		/* Update start cluster */
		st_qword( fs->dirbuf + XDIR_FileSize, obj->sizebytes );		/* Update file size */
		st_qword( fs->dirbuf + XDIR_ValidFileSize, obj->sizebytes );	/* (FatFs does not support Valid File Size feature) */
		st_dword( fs->dirbuf + XDIR_ModTime, tm );		/* Update modified time */
		fs->dirbuf[XDIR_ModTime10] = 0;
		st_dword( fs->dirbuf + XDIR_AccTime, 0 );

		if( store_xdir( &dj ) != FR_OK )	/* Restore it to the directory */
			return SetErrNo( EINVAL, -1 ); //FIXME
		}
	else
		{
		uint8_t	*dir;

		if( move_window( fs, obj->sectorofdirentry ) != FR_OK )
			return SetErrNo( EINVAL, -1 ); //FIXME

		dir				= obj->ptrtodirentry;
		dir[DIR_Attr]  |= FFS_FILEATTRIBUTE_ARCHIVE;							/* Set archive attribute to indicate that the file has been changed */
		st_clust( obj->fs, dir, obj->startcluster );		/* Update file allocation information  */
		st_dword( dir + DIR_FileSize, (uint32_t)obj->sizebytes );	/* Update file size */
		st_dword( dir + DIR_ModTime, tm );					/* Update modified time */
		st_word( dir + DIR_LstAccDate, 0 );
		fs->windowisdirty = 1;
		}

	if( sync_fs( fs ) != FR_OK )
		{
		obj->statusflags &= (uint8_t)~FA_MODIFIED;
		return SetErrNo( EINVAL, -1 ); //FIXME
		}

	obj->statusflags &= (uint8_t)~FA_MODIFIED;

	return 0;
	}
/*----------------------------------------------------------------------------*/
static int _fsync_a( int fd, int salt )
	{
	ffs_file_t *fp;

	fp	= &FFS_Files[fd];

	if( !ValidateObject( fp->obj ))
		return SetErrNo( ENFILE, -1 );

	if( salt != fp->salt )
		return SetErrNo( ENFILE, -1 );

	return _fsync_b( fp );
	}
/*----------------------------------------------------------------------------*/
int _fsync( int fd )
	{
	int	result, salt;

	/* The system mutex is NULL, meaning that the file system was not initialized,
	   so there are no logical drives mounted and there is nothing we can do here... */
	if( FFS_SystemMutex == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	salt	= fd >> 8;
	fd 		= ( fd & 0xff ) - FFS_RESERVED_FILES;

	if( !FileIndexIsValid( fd ))
		return SetErrNo( ENFILE, -1 );

	if( !FFS_TakeMutex( FFS_SystemMutex ))
		return SetErrNo( EBUSY, -1 );

	result	= _fsync_a( fd, salt );

	FFS_ReleaseMutex( FFS_SystemMutex );

	return result;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Close File                                                            */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static int _close_a( int fd, int salt )		/* Pointer to the file object to be closed */
	{
	ffs_file_t		*fp;
	ffs_object_t	*obj;
	int				result;

	fp	= &FFS_Files[fd];
	obj	= fp->obj;

	if( !ValidateObject( obj ))
		return SetErrNo( EINVAL, -1 );	//FIXED
	
	if( salt != fp->salt )
		return SetErrNo( EINVAL, -1 );	//FIXED

	if(( disk_status( obj->fs->pdrv ) & STA_NOINIT ))
		return SetErrNo( ENFILE, -1 );	//FIXME: FR_DISK_ERR

	result	= _fsync_b( fp );					/* Flush cached data */

	if( result != 0 )
		return SetErrNo( ENFILE, -1 );	//FIXME: FR_DISK_ERR

	/* This is the last reference to the object... */
	if( --obj->refcounter == 0 )
		ReturnFreeObjectIdx( fp->objectidx );
	/* Still there are references to the object... */
	else
		{
		uint8_t	access, mode;

		access	= obj->access;
		if( access >= A_READ && access <= A_APPEND )
			{
			access -= A_READ;
			if( obj->acccntrs[access] > 0 )
				obj->acccntrs[access]--;
			}
		if( obj->acccntrs[ACCESSCOUNTERS_INDEX_READ] != 0 )
			obj->access	= A_READ;
		else if( obj->acccntrs[ACCESSCOUNTERS_INDEX_APPEND] != 0 )
			obj->access	= A_APPEND;
		else
			obj->access	= A_WRITE;

		mode	= obj->mode;
		if( mode >= M_APPEND && mode <= M_WRITE )
			{
			mode -= M_APPEND;
			if( obj->modecntrs[mode] > 0 )
				obj->modecntrs[mode]--;
			}
		if( obj->modecntrs[MODECOUNTERS_INDEX_WRITE] != 0 )
			obj->mode	= M_WRITE;
		else if( obj->modecntrs[MODECOUNTERS_INDEX_APPEND] != 0 )
			obj->mode	= M_APPEND;
		else
			obj->mode	= M_READ;
		}

	fp->obj = NULL;				/* Invalidate file object */

	ReturnFreeFileIdx( fd );
	
	return 0;
	}
/*----------------------------------------------------------------------------*/
int _close( int fd )
	{
	int	result, salt;

	/* The system mutex is NULL, meaning that the file system was not initialized,
	   so there are no logical drives mounted and there is nothing we can do here... */
	if( FFS_SystemMutex == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	salt	= fd >> 8;
	fd 		= ( fd & 0xff ) - FFS_RESERVED_FILES;

	if( !FileIndexIsValid( fd ))
		return SetErrNo( ENFILE, -1 );

	if( !FFS_TakeMutex( FFS_SystemMutex ))
		return SetErrNo( EBUSY, -1 );

	result	= _close_a( fd, salt );
	
	FFS_ReleaseMutex( FFS_SystemMutex );

	return result;
	}
/*============================================================================*/
static int _eof_a( int fd, int salt )		/* Pointer to the file object to be closed */
	{
	ffs_file_t		*fp;
	ffs_object_t	*obj;
	int				result;

	fp	= &FFS_Files[fd];
	obj	= fp->obj;

	if( !ValidateObject( obj ))
		return SetErrNo( EINVAL, -1 );	//FIXED
	
	if( salt != fp->salt )
		return SetErrNo( EINVAL, -1 );	//FIXED

	return 0;
	}
/*============================================================================*/
int _eof( int fd )
	{
	int	result, salt;

	/* The system mutex is NULL, meaning that the file system was not initialized,
	   so there are no logical drives mounted and there is nothing we can do here... */
	if( FFS_SystemMutex == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	salt	= fd >> 8;
	fd 		= ( fd & 0xff ) - FFS_RESERVED_FILES;

	if( !FileIndexIsValid( fd ))
		return SetErrNo( ENFILE, -1 );

	if( !FFS_TakeMutex( FFS_SystemMutex ))
		return SetErrNo( EBUSY, -1 );

	result	= _eof_a( fd, salt );
	
	FFS_ReleaseMutex( FFS_SystemMutex );

	return result;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Change Current Directory or Current Drive, Get Current Directory      */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static int _chdrive_a( const ffs_char_t *ldn )		/* Drive number */
	{
	int volidx;

	/* Get logical drive */
	if( !VolumeIndexIsValid( volidx = FindVolumeIndexFromName( ldn )))
		return SetErrNo( EINVAL, -1 ); //FIXME

	FFS_CurrentVolumeIdx = volidx;	/* Set it as current volume */

	return 0;
	}
/*----------------------------------------------------------------------------*/
int _chdrive( const ffs_char_t *ldname )
	{
	ffs_char_t	ldn[6];
	int			result;

	/* The system mutex is NULL, meaning that the file system was not initialized,
	   so there are no logical drives mounted and there is nothing we can do here... */
	if( FFS_SystemMutex == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	if( ldname == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	/* This operation is safe to do before acquiring the mutex because it only
	   uses local data.*/
	if( ParseLogicalDriveName( &ldname, ldn, sizeof ldn ) <= 0 )
		return SetErrNo( ENODEV, -1 ); //FIXME

	if( *ldname != '\0' )
		return SetErrNo( ENODEV, -1 ); //FIXME

	/* The system mutex is NULL, meaning that the file system was not initialized,
	   so there are no logical drives mounted and there is nothing we can do here... */
	if( FFS_SystemMutex == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	result	= _chdrive_a( ldn );

	FFS_ReleaseMutex( FFS_SystemMutex );

	return result;
	}
/*============================================================================*/
static int _chdir_a( const ffs_char_t *ldn, const ffs_char_t *path )	/* Pointer to the directory path */
	{
	ffs_volume_t	*fs;
	ffs_directory_t	dj;
	ffs_object_t	djobj;
	ffs_fresult_t	res;

	/* Get logical drive */
	if(( fs = FindVolumeFromName( ldn )) == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	dj.obj			= &djobj;
	dj.objectidx	= INVALID_OBJECT_INDEX;
	dj.obj->fs		= fs;

	if(( res = follow_path( &dj, path )) != FR_OK )
		{
		if( res == FR_NO_FILE )
			return SetErrNo( EINVAL, -1 ); //FIXME: FR_NO_PATH
		return SetErrNo( EINVAL, -1 ); //FIXME
		}

	if( dj.fn[NSFLAG] & NS_NONAME )  	/* Is it the start directory itself? */
		{
		fs->curdirstartcluster	= dj.obj->startcluster;
		if( fs->fs_type == FFS_FILESYSTEMTYPE_EXFAT )
			{
			fs->parentdirstartcluster	= dj.obj->parentdirstartcluster;
			fs->parentdirsize			= dj.obj->parentdirsize;
			fs->offsetinparentdir		= dj.obj->offsetinparentdir;
			}
		}
	else
		{
		if( !( dj.obj->attributes & FFS_FILEATTRIBUTE_DIRECTORY ))  	/* It is a sub-directory */
			return SetErrNo( EINVAL, -1 ); //FIXME: FR_NO_PATH		/* Reached but a file */

		if( fs->fs_type == FFS_FILESYSTEMTYPE_EXFAT )
			{
			fs->curdirstartcluster		= ld_dword( fs->dirbuf + XDIR_FstClus );	/* Sub-directory cluster */
			fs->parentdirstartcluster	= dj.obj->startcluster;						/* Save containing directory information */
			fs->parentdirsize			= ((uint32_t)dj.obj->sizebytes & 0xFFFFFF00 ) | dj.obj->chainstatus;
			fs->offsetinparentdir		= dj.blk_ofs;
			}
		else
			fs->curdirstartcluster	= ld_clust( fs, dj.dir );					/* Sub-directory cluster */
		}

	return res;
	}
/*----------------------------------------------------------------------------*/
int _chdir( const ffs_char_t *path )	/* Pointer to the directory path */
	{
	ffs_char_t	ldn[6];
	int			result;

	/* The system mutex is NULL, meaning that the file system was not initialized,
	   so there are no logical drives mounted and there is nothing we can do here... */
	if( FFS_SystemMutex == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	if( path == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	/* This operation is safe to do before acquiring the mutex because it only
	   uses local data.*/
	if( ParseLogicalDriveName( &path, ldn, sizeof ldn ) < 0 )
		return SetErrNo( ENODEV, -1 ); //FIXME

	/* The system mutex is NULL, meaning that the file system was not initialized,
	   so there are no logical drives mounted and there is nothing we can do here... */
	if( FFS_SystemMutex == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	result	= _chdir_a( ldn, path );

	FFS_ReleaseMutex( FFS_SystemMutex );

	return result;
	}
/*============================================================================*/
static int _getcwd_a( const ffs_char_t *ldn, ffs_char_t *buff, unsigned int len )	/* Pointer to the directory path */ /* Size of path */
	{
	ffs_volume_t	*fs;
	ffs_directory_t	dj;
	ffs_object_t	djobj;
	unsigned int	i, n;
	ffs_char_t		*tp = buff;
	unsigned int	vl;
	const char		*vp;
	ffs_fileinfo_t	fno;

	*buff	= '\0';

	/* Get logical drive */
	if(( fs = FindVolumeFromName( ldn )) == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	dj.obj			= &djobj;
	dj.objectidx	= INVALID_OBJECT_INDEX;
	dj.obj->fs		= fs;

	i	= len;							/* Bottom of buffer (directory stack base) */

	/* Follow parent directories and create the path */

	if( fs->fs_type != FFS_FILESYSTEMTYPE_EXFAT )  	/* (Cannot do getcwd on exFAT and returns root path) */
		{
		uint32_t	ccl;

		dj.obj->startcluster = fs->curdirstartcluster;	/* Start to follow upper directory from current directory */
		while(( ccl = dj.obj->startcluster ) != 0 )  	/* Repeat while current directory is a sub-directory */
			{
			if( dir_sdi( &dj, 1 * SIZE_OF_DIR_ENTRY ) != FR_OK )	/* Get parent directory */
				return SetErrNo( EINVAL, -1 ); //FIXME
			if( move_window( fs, dj.sect ) != FR_OK )
				return SetErrNo( EINVAL, -1 ); //FIXME

			dj.obj->startcluster = ld_clust( fs, dj.dir );	/* Goto parent directory */

			if( dir_sdi( &dj, 0 ) != FR_OK )
				return SetErrNo( EINVAL, -1 ); //FIXME

			while( 1 )		 							/* Find the entry links to the child directory */
				{
				ffs_fresult_t	res;

				if( dir_read_file( &dj ) != FR_OK )
					return SetErrNo( EINVAL, -1 ); //FIXME
				if( ccl == ld_clust( fs, dj.dir ))
					break;	/* Found the entry */
				if(( res = dir_next( &dj, 0 )) != FR_OK )
					{
					if( res == FR_NO_FILE )				/* It cannot be 'not found'. */
						return SetErrNo( EINVAL, -1 );	//FIXME: FR_INT_ERR
					return SetErrNo( EINVAL, -1 );	//FIXME
					}
				}

			get_fileinfo( &dj, &fno );		/* Get the directory name and push it to the buffer */
			for( n = 0; fno.fname[n] != '\0'; n++ )	/* Name length */
				{}
			if( i < n + 1 )  		/* Insufficient space to store the path name? */
				return SetErrNo( EINVAL, -1 );	//FIXME: FR_NOT_ENOUGH_CORE
			while( n )
				buff[--i] = fno.fname[--n];	/* Stack the name */
			buff[--i] = '/';
			}
		}

	if( i == len )
		buff[--i] = '/';	/* Is it the root-directory? */
	vl = 0;
	for( n = 0, vp = (const char*)ldn; vp[n]; n++ )
		{}
	if( i >= n + 2 )
		{
		for( vl = 0; vl < n; *tp++ = (ffs_char_t)vp[vl], vl++ )
			{}
		*tp++ = (ffs_char_t)':';
		vl++;
		}

	if( vl == 0 )
		return SetErrNo( EINVAL, -1 );	//FIXME: FR_NOT_ENOUGH_CORE;

	/* Add current directory path */
	do  	/* Copy stacked path string */
		{
		*tp++ = buff[i++];
		}
	while( i < len );

	*tp	= 0;

	return 0;
	}
/*----------------------------------------------------------------------------*/
int ffs_getcwd( const ffs_char_t *ldname, ffs_char_t *buff, size_t len )
	{
	ffs_char_t	ldn[6];
	int			result;

	/* The system mutex is NULL, meaning that the file system was not initialized,
	   so there are no logical drives mounted and there is nothing we can do here... */
	if( FFS_SystemMutex == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	/* This operation is safe to do before acquiring the mutex because it only
	   uses local data.*/
	if( ParseLogicalDriveName( &ldname, ldn, sizeof ldn ) < 0 )
		return SetErrNo( ENODEV, -1 ); //FIXME
	
	/* There are extra characters after the logical volume name... */
	if( ldname != NULL && *ldname != '\0' )
		return SetErrNo( ENODEV, -1 ); //FIXME

	if( !FFS_TakeMutex( FFS_SystemMutex ))
		return SetErrNo( EBUSY, -1 );

	result	= _getcwd_a( ldn, buff, len );
	
	FFS_ReleaseMutex( FFS_SystemMutex );

	return result;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Seek File Read/Write Pointer                                          */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static ffs_off_t _lseek_b( ffs_file_t *fp, ffs_off_t ofs, int whence ) /* Pointer to the file object */ /* File pointer from top of file */
	{
	lba_t			nsect;
	filesize_t		ifptr;
	ffs_volume_t	*fs;
	ffs_object_t	*obj;
	uint32_t		clst, bcs;

	obj	= fp->obj;
	fs	= obj->fs;

	if(( disk_status( fs->pdrv ) & STA_NOINIT ))
		return SetErrNo( ENFILE, -1 );	//FIXME: FR_DISK_ERR

	if( whence == SEEK_SET )
		{
		if( ofs < 0 || ofs > obj->sizebytes )
			return SetErrNo( EINVAL, -1 );
		}
	else if( whence == SEEK_END )
		{
		if( ofs > 0 || ofs < -obj->sizebytes )
			return SetErrNo( EINVAL, -1 );
		else
			ofs	= obj->sizebytes + ofs;
		}
	else if( whence == SEEK_CUR )
		{
		if( fp->fptr + ofs < 0 || fp->fptr + ofs > obj->sizebytes )
			return SetErrNo( EINVAL, -1 );
		else
			ofs	= fp->fptr + ofs;
		}
	else
		return SetErrNo( EINVAL, -1 );

	if( fs->fs_type == FFS_FILESYSTEMTYPE_EXFAT && fill_last_frag( obj, fp->currentcluster, 0xFFFFFFFF ) != FR_OK )	/* Fill last fragment on the FAT if needed */
		return SetErrNo( EINVAL, -1 );	//FIXME

	/* Normal Seek */
	if( fs->fs_type != FFS_FILESYSTEMTYPE_EXFAT && ofs >= 0x100000000 )
		ofs = 0xFFFFFFFF;	/* Clip at 4 GiB - 1 if at FATxx */
	if( ofs > obj->sizebytes && ( !( fp->mode & _FWRITE )))  	/* In read-only mode, clip offset with the file size */
		ofs = obj->sizebytes;
	ifptr = fp->fptr;
	fp->fptr = nsect = 0;
	if( ofs > 0 )
		{
		bcs = (uint32_t)fs->sectorspercluster * fs->bytespersector;	/* Cluster size (byte) */
		if( ifptr > 0 &&
				( ofs - 1 ) / bcs >= ( ifptr - 1 ) / bcs )  	/* When seek to same or following cluster, */
			{
			fp->fptr = ( ifptr - 1 ) & ~(filesize_t)( bcs - 1 );	/* start from the current cluster */
			ofs -= fp->fptr;
			clst = fp->currentcluster;
			}
		else  									/* When seek to back cluster, */
			{
			clst = obj->startcluster;					/* start from the first cluster */
			if( clst == 0 )  						/* If no cluster chain, create a new chain */
				{
				clst = create_chain( obj, 0 );
				if( clst == 1 )
					{
					fp->err = (uint8_t)FR_INT_ERR;
					return SetErrNo( EINVAL, -1 );	//FIXME: FR_INT_ERR;
					}
				if( clst == 0xFFFFFFFF )
					{
					fp->err = (uint8_t)FR_DISK_ERR;
					return SetErrNo( EINVAL, -1 );	//FIXME: FR_DISK_ERR;
					}
				obj->startcluster = clst;
				}
			fp->currentcluster = clst;
			}
		if( clst != 0 )
			{
			while( ofs > bcs )  						/* Cluster following loop */
				{
				ofs -= bcs;
				fp->fptr += bcs;
				if( fp->mode & _FWRITE )  			/* Check if in write mode or not */
					{
					if( fp->fptr > obj->sizebytes )  	/* No FAT chain object needs correct sizebytes to generate FAT value */
						{
						obj->sizebytes		= fp->fptr;
						obj->statusflags   |= FA_MODIFIED;
						}
					clst = create_chain( obj, clst );	/* Follow chain with forceed stretch */
					if( clst == 0 )  				/* Clip file size in case of disk full */
						{
						ofs = 0;
						break;
						}
					}
				else
					{
					clst = get_fat( obj, clst );	/* Follow cluster chain if not in write mode */
					}
				if( clst == 0xFFFFFFFF )
					{
					fp->err = (uint8_t)FR_DISK_ERR;
					return SetErrNo( EINVAL, -1 );	//FIXME: FR_DISK_ERR;
					}
				if( clst < FIRST_VALID_CLUSTER || clst >= fs->fatentriescount )
					{
					fp->err = (uint8_t)FR_INT_ERR;
					return SetErrNo( EINVAL, -1 );	//FIXME: FR_INT_ERR;
					}
				fp->currentcluster = clst;
				}
			fp->fptr += ofs;
			if( ofs % fs->bytespersector )
				{
				nsect = clst2sect( fs, clst );	/* Current sector */
				if( nsect == 0 )
					{
					fp->err = (uint8_t)FR_INT_ERR;
					return SetErrNo( EINVAL, -1 );	//FIXME: FR_INT_ERR;
					}
				nsect += (uint32_t)( ofs / fs->bytespersector );
				}
			}
		}
	if( fp->fptr > obj->sizebytes )  	/* Set file change flag if the file size is extended */
		{
		obj->sizebytes		= fp->fptr;
		obj->statusflags   |= FA_MODIFIED;
		}
	if( fp->fptr % fs->bytespersector && nsect != obj->sectorinbuf )  	/* Fill sector cache if needed */
		{
		if( obj->statusflags & FA_DIRTY )  			/* Write-back dirty sector cache */
			{
			if( disk_write( fs->pdrv, obj->buf, obj->sectorinbuf, 1 ) != RES_OK )
				{
				fp->err = (uint8_t)FR_DISK_ERR;
				return SetErrNo( EINVAL, -1 );	//FIXME: FR_DISK_ERR;
				}
			obj->statusflags &= (uint8_t)~FA_DIRTY;
			}
		if( disk_read( fs->pdrv, obj->buf, nsect, 1 ) != RES_OK )
			{
			fp->err = (uint8_t)FR_DISK_ERR;
			return SetErrNo( EINVAL, -1 );	//FIXME: FR_DISK_ERR;
			}	/* Fill sector cache */
		obj->sectorinbuf = nsect;
		}

	return fp->fptr;
	}
/*----------------------------------------------------------------------------*/
static ffs_off_t _lseek_a( int fd, int salt, ffs_off_t ofs, int whence ) /* Pointer to the file object */ /* File pointer from top of file */
	{
	ffs_file_t	*fp;		/* Pointer to the file object */

	fp	= &FFS_Files[fd];

	if( !ValidateObject( fp->obj ))
		return SetErrNo( ENFILE, -1 );

	if( salt != fp->salt )
		return SetErrNo( ENFILE, -1 );

	if( fp->err != FR_OK )
		return SetErrNo( ENFILE, -1 );	//FIXME

	return _lseek_b( fp, ofs, whence );
	}
/*----------------------------------------------------------------------------*/
#if         !defined FFS_CONFIG_USELBA64 || FFS_CONFIG_USELBA64 == 0
ffs_off_t _lseek( int fd, off_t offset, int whence )
#else   /*  !defined FFS_CONFIG_USELBA64 || FFS_CONFIG_USELBA64 == 0 */
_off64_t _lseek64( int fd, _off64_t offset, int whence );
ffs_off_t _lseek( int fd, off_t offset, int whence )
	{
	return (off_t)_lseek64( fd, (_off64_t)offset, whence );
	}
_off64_t _lseek64( int fd, _off64_t offset, int whence )
#endif  /*  !defined FFS_CONFIG_USELBA64 || FFS_CONFIG_USELBA64 == 0 */
	{
	int	result, salt;

	/* The system mutex is NULL, meaning that the file system was not initialized,
	   so there are no logical drives mounted and there is nothing we can do here... */
	if( FFS_SystemMutex == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	salt	= fd >> 8;
	fd 		= ( fd & 0xff ) - FFS_RESERVED_FILES;

	if( !FileIndexIsValid( fd ))
		return SetErrNo( ENFILE, -1 );

	if( !FFS_TakeMutex( FFS_SystemMutex ))
		return SetErrNo( EBUSY, -1 );

	result	= _lseek_a( fd, salt, offset, whence );

	FFS_ReleaseMutex( FFS_SystemMutex );

	return result;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Create a Directory Object                                             */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
size_t SizeOfDirObject( void )
	{
	return sizeof( ffs_directory_t );
	}
/*============================================================================*/
static int _opendir_b( ffs_volume_t *fs, ffs_directory_t *dp, const ffs_char_t *ldn, const ffs_char_t *path )
	{
	int				dpobjidx;
	ffs_object_t	dpobj;
	ffs_fresult_t	res;

	/* For now we use a local object, until we find that the directory is open
	   or we get a new object. CppCheck detects this as unsafe code and it is,
	   but we will change this value to NULL in case of error or make it point
	   to a different object in case of success. */
	dp->obj				= &dpobj;
	dp->objectidx		= INVALID_OBJECT_INDEX;
	dp->obj->fs			= fs;
	dp->obj->fsmountid	= fs->mountid;

	if(( res = follow_path( dp, path )) != FR_OK )			/* Follow the path to the directory */
		{
		if( res == FR_NO_FILE )
			return SetErrNo( EINVAL, -1 );	//FIXME: FR_NO_PATH
		return SetErrNo( EINVAL, -1 );		//FIXME
		}
	
	if( !( dp->fn[NSFLAG] & NS_NONAME ))  	/* It is not the origin directory itself */
		{
		if( !( dp->obj->attributes & FFS_FILEATTRIBUTE_DIRECTORY ))  	/* This object is not a sub-directory */
			return SetErrNo( EINVAL, -1 );	//FIXME: FR_NO_PATH

		dp->obj->parentdirstartcluster	= dp->obj->startcluster;	/* Get containing directory inforamation */
		dp->obj->parentdirsize	= ( (uint32_t)dp->obj->sizebytes & 0xFFFFFF00 ) | dp->obj->chainstatus;
		dp->obj->offsetinparentdir	= dp->blk_ofs;
			
		if( fs->fs_type == FFS_FILESYSTEMTYPE_EXFAT )
			init_alloc_info( fs, dp->obj );	/* Get object allocation info */
		else
			dp->obj->startcluster	= ld_clust( fs, dp->dir );	/* Get object allocation info */
		}

	if( dir_sdi( dp, 0 ) != FR_OK )			/* Rewind directory */
		return SetErrNo( EINVAL, -1 );	//FIXME

	/* The directory is not open yet... */
	if( !ObjectIndexIsValid( dpobjidx = SearchObject( dp )))
		{
		/* ...we need to acquire an object to use with it... */
		if( !ObjectIndexIsValid( dpobjidx = GetFreeObjectIdx() ))
			/* ...but there are no more free objects. */
			return SetErrNo( EINVAL, -1 ); //FIXME

		dp->obj			= &FFS_Objects[dpobjidx];
		dp->objectidx	= dpobjidx;
		memcpy( dp->obj, &dpobj, sizeof dpobj );
		}
	/* The directory is open already... */
	else
		{
		/* ...but in a mode that does not allow us to open it for
		   reading. That should not happen, but... */
		if( dp->obj->access < A_READ )
			return SetErrNo( EACCES, -1 ); //FIXME

		dp->obj			= &FFS_Objects[dpobjidx];
		dp->objectidx	= dpobjidx;
		}

	/* We are not requesting open mode more than 'M_READ' and are not
	   restricting access to less than A_WRITE, so we can just increment
	   the reference counter. */
	dp->obj->refcounter++;

	return 0;
	}
/*----------------------------------------------------------------------------*/
static int _opendir_a( ffs_directory_t *dp, const ffs_char_t *ldn, const ffs_char_t *path )
	{
	ffs_volume_t	*fs;
	int				result;

	/* Get logical drive */
	if(( fs = FindVolumeFromName( ldn )) == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	/* Call the function that will do the real work. If it fails... */
	if(( result = _opendir_b( fs, dp, ldn, path )) == -1 )
		{
		/* ...we must clean 'dp->obj' so we do not leak the address of an auto
		   variable on stack... */
		dp->obj			= NULL;
		/* ...and also invalidate the index so nobody else will try to release it. */
		dp->objectidx	= INVALID_OBJECT_INDEX;
		}

	return result;
	}
/*----------------------------------------------------------------------------*/
int opendir( void *dp, const ffs_char_t *path )
	{
	ffs_char_t	ldn[6];
	int			result;

	if( dp == NULL )
		return SetErrNo( EINVAL, -1 );	//FIXED

	/* Let's zero the directory object so that if 'opendir' fails and somebody tries
	   to use it even so with 'readdir', we will avoid a catastrophic error later. */
	memset( dp, 0, sizeof( ffs_directory_t ));

	/* The system mutex is NULL, meaning that the file system was not initialized,
	   so there are no logical drives mounted and there is nothing we can do here... */
	if( FFS_SystemMutex == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	/* This operation is safe to do before acquiring the mutex because it only
	   uses local data.*/
	if( ParseLogicalDriveName( &path, ldn, sizeof ldn ) < 0 )
		return SetErrNo( ENODEV, -1 ); //FIXME

	/* From now on we must have exclusive access to the file system... */
	if( !FFS_TakeMutex( FFS_SystemMutex ))
		/* ...but it was hogged for too long by another task. */
		return SetErrNo( EBUSY, -1 );

	/* Call the function that is going to do the real dirty work. */
	result	= _opendir_a( dp, ldn, path );

	/* Release the mutex, because we don't want to hang the system. */
	FFS_ReleaseMutex( FFS_SystemMutex );

	return result;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Close Directory                                                       */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static int _closedir_a( ffs_directory_t *dp ) /* Pointer to the directory object to be closed */
	{
	if( !ValidateObject( dp->obj ))
		return SetErrNo( ENFILE, -1 );

	if( ObjectIndexIsValid( dp->objectidx ) && --dp->obj->refcounter == 0 )
		ReturnFreeObjectIdx( dp->objectidx );

	return 0;
	}
/*----------------------------------------------------------------------------*/
int closedir( void *dp ) /* Pointer to the directory object to be closed */
	{
	int	result;

	/* The system mutex is NULL, meaning that the file system was not initialized,
	   so there are no logical drives mounted and there is nothing we can do here... */
	if( FFS_SystemMutex == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	if( dp == NULL )
		return SetErrNo( EINVAL, -1 );	//FR_INVALID_OBJECT

	if( !FFS_TakeMutex( FFS_SystemMutex ))
		return SetErrNo( EBUSY, -1 );

	result	= _closedir_a( dp );
	
	FFS_ReleaseMutex( FFS_SystemMutex );

	memset( dp, 0, sizeof( ffs_directory_t ));

	return result;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Read Directory Entries in Sequence                                    */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static int _readdir_a( ffs_directory_t *dp, ffs_fileinfo_t *fno )	/* Pointer to the open directory object */ /* Pointer to file information to return */
	{
	ffs_fresult_t	res;
	if( !ValidateObject( dp->obj ))
		return SetErrNo( EINVAL, -1 ); //FIXME

	if( fno == NULL )
		{
		if( dir_sdi( dp, 0 ) != FR_OK )		/* Rewind the directory object */
			return SetErrNo( EINVAL, -1 ); //FIXME
		return 0;
		}

	res = dir_read_file( dp );			/* Read an item */
	if( res == FR_NO_FILE )
		{
		memset( fno, 0, sizeof *fno );
		return 0;
		}
	if( res != FR_OK )
		return SetErrNo( EINVAL, -1 ); //FIXME

	get_fileinfo( dp, fno );		/* Get the object information */

	res = dir_next( dp, 0 );		/* Increment index for next */
	if( res != FR_NO_FILE && res != FR_OK )	/* Ignore end of directory now */
		return SetErrNo( EINVAL, -1 ); //FIXME

	return 1;
	}
/*----------------------------------------------------------------------------*/
int readdir( void *dp, ffs_fileinfo_t *fno )	/* Pointer to the open directory object */ /* Pointer to file information to return */
	{
	int	result;

	/* The system mutex is NULL, meaning that the file system was not initialized,
	   so there are no logical drives mounted and there is nothing we can do here... */
	if( FFS_SystemMutex == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	if( dp == NULL )
		return SetErrNo( EINVAL, -1 );	//FIXED

	if( !FFS_TakeMutex( FFS_SystemMutex ))
		return SetErrNo( EBUSY, -1 );

	result	= _readdir_a( (ffs_directory_t*)dp, fno );
	
	FFS_ReleaseMutex( FFS_SystemMutex );

	return result;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Find Next File                                                        */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
int findnext( void *dp, ffs_fileinfo_t *fno )	/* Pointer to the open directory object */ /* Pointer to the file information structure */
	{
	while( 1 )
		{
		if( readdir( dp, fno ) != 0 )		/* Get a directory item */
			return -1;
		if( fno == NULL || fno->fname[0] == '\0' )	/* Terminate if end of directory */
			return 0;
		if( pattern_match( ( (ffs_directory_t*)dp )->pat, fno->fname, 0, FIND_RECURS ))
			return 1;		/* Test for the file name */
		if( pattern_match( ( (ffs_directory_t*)dp )->pat, fno->altname, 0, FIND_RECURS ))
			return 1;		/* Test for alternative name if exist */
		}
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Find First File                                                       */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
int findfirst( void *dp, ffs_fileinfo_t *fno, const ffs_char_t *path, const ffs_char_t *pattern ) /* Pointer to the blank directory object */ /* Pointer to the file information structure */ /* Pointer to the directory to open */ /* Pointer to the matching pattern */
	{
	if( opendir( dp, path ) != 0 )	/* Open the target directory */
		return -1;

	( (ffs_directory_t*)dp )->pat = pattern;					/* Save pointer to pattern string */

	return findnext( dp, fno );		/* Find the first item */
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Get File Status                                                       */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static int _fstat_a( const ffs_char_t *ldn, const ffs_char_t *path, ffs_fileinfo_t *fno )	/* Pointer to the file path */ /* Pointer to file information to return */
	{
	ffs_directory_t	dj;
	ffs_object_t	djobj;

	dj.obj			= &djobj;
	dj.objectidx	= INVALID_OBJECT_INDEX;

	/* Get logical drive */
	if(( dj.obj->fs = FindVolumeFromName( ldn )) == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME


	if( follow_path( &dj, path ) != FR_OK )	/* Follow the file path */
		return SetErrNo( ENOENT, -1 ); //FIXED

	if( dj.fn[NSFLAG] & NS_NONAME )  	/* It is origin directory */
		return SetErrNo( EINVAL, -1 ); //FIXME: FR_INVALID_NAME;

	if( fno != NULL )
		get_fileinfo( &dj, fno );

	return 0;
	}
/*----------------------------------------------------------------------------*/
int ffs_fstat( const ffs_char_t *path, ffs_fileinfo_t *fno )
	{
	ffs_char_t	ldn[6];
	int			result;

	/* The system mutex is NULL, meaning that the file system was not initialized,
	   so there are no logical drives mounted and there is nothing we can do here... */
	if( FFS_SystemMutex == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	if( path == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	/* This operation is safe to do before acquiring the mutex because it only
	   uses local data.*/
	if( ParseLogicalDriveName( &path, ldn, sizeof ldn ) < 0 )
		return SetErrNo( ENODEV, -1 ); //FIXME

	if( !FFS_TakeMutex( FFS_SystemMutex ))
		return SetErrNo( EBUSY, -1 );

	result	= _fstat_a( ldn, path, fno );
	
	FFS_ReleaseMutex( FFS_SystemMutex );

	return result;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Get Number of Free Clusters                                           */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static filesize_t _getfree_a( const ffs_char_t *volname )		/* Path name of the logical drive number */
	{
	lba_t				sect;
	ffs_volume_t		*fs;
	uint32_t			nfree, clst;
	unsigned int		i;
	ffs_object_t		obj;

	if(( fs = FindVolumeFromName( volname )) == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	/* If freeclusterscount is valid, return it without full FAT scan */
	if( fs->freeclusterscount <= fs->fatentriescount - FIRST_VALID_CLUSTER )
		return fs->freeclusterscount;

	/* Scan FAT to obtain number of free clusters */
	nfree = 0;
	if( fs->fs_type == FFS_FILESYSTEMTYPE_FAT12 )  	/* FAT12: Scan bit field FAT entries */
		{
		clst	= FIRST_VALID_CLUSTER;
		obj.fs	= fs;
		do
			{
			uint32_t	stat;

			stat = get_fat( &obj, clst );
			if( stat == 0xFFFFFFFF )
				return SetErrNo( EINVAL, -1 ); //FIXME: FR_DISK_ERR;
			if( stat == 1 )
				return SetErrNo( EINVAL, -1 ); //FIXME: FR_INT_ERR;
			if( stat == 0 )
				nfree++;
			}
		while( ++clst < fs->fatentriescount );
		}
	else
		{
		if( fs->fs_type == FFS_FILESYSTEMTYPE_EXFAT )  	/* exFAT: Scan allocation bitmap */
			{
			unsigned int	b;
			uint8_t			bm;

			clst = fs->fatentriescount - FIRST_VALID_CLUSTER;	/* Number of clusters */
			sect = fs->bitbase;			/* Bitmap sector */
			i = 0;						/* Offset in the sector */
			do  	/* Counts numbuer of bits with zero in the bitmap */
				{
				if( i == 0 && move_window( fs, sect++ ) != FR_OK )	/* New sector? */
					return SetErrNo( EINVAL, -1 ); //FIXME

				for( b = 8, bm = ~fs->win[i]; b && clst; b--, clst-- )
					{
					nfree += bm & 1;
					bm >>= 1;
					}
				i = ( i + 1 ) % fs->bytespersector;
				}
			while( clst );
			}
		else
			{
			/* FAT16/32: Scan uint16_t/uint32_t FAT entries */
			clst = fs->fatentriescount;	/* Number of entries */
			sect = fs->fatbase;		/* Top of the FAT */
			i = 0;					/* Offset in the sector */
			do  	/* Counts numbuer of entries with zero in the FAT */
				{
				if( i == 0 && move_window( fs, sect++ ) != FR_OK )	/* New sector? */
					return SetErrNo( EINVAL, -1 ); //FIXME

				if( fs->fs_type == FFS_FILESYSTEMTYPE_FAT16 )
					{
					if( ld_word( fs->win + i ) == 0 )
						nfree++;
					i += 2;
					}
				else
					{
					if(( ld_dword( fs->win + i ) & 0x0FFFFFFF ) == 0 )
						nfree++;
					i += 4;
					}
				i %= fs->bytespersector;
				}
			while( --clst );
			}
		}

	fs->freeclusterscount	= nfree;	/* Now freeclusterscount is valid */
	fs->fsi_flag   |= 1;		/* FAT32: FSInfo is to be updated */

	return nfree;
	}
/*----------------------------------------------------------------------------*/
ssize_t getfree( const ffs_char_t *ldname )
	{
	ffs_char_t	ldn[6];
	int			result;

	/* The system mutex is NULL, meaning that the file system was not initialized,
	   so there are no logical drives mounted and there is nothing we can do here... */
	if( FFS_SystemMutex == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	if( ParseLogicalDriveName( &ldname, ldn, sizeof ldn ) < 0 )
		return SetErrNo( ENODEV, -1 ); //FIXME

	/* There are extra characters after the logical volume name... */
	if( ldname != NULL && *ldname != '\0' )
		return SetErrNo( ENODEV, -1 ); //FIXME

	/* From now on we must have exclusive access to the file system... */
	if( !FFS_TakeMutex( FFS_SystemMutex ))
		/* ...but it was hogged for too long by another task. */
		return SetErrNo( EBUSY, -1 );

	/* Call the function that is going to do the real dirty work. */
	result	= _getfree_a( ldn );

	/* Release the mutex, because we don't want to hang the system. */
	FFS_ReleaseMutex( FFS_SystemMutex );

	return result;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Truncate File                                                         */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static int _truncate_a( int fd, int salt, ffs_off_t len )		/* Pointer to the file object */
	{
	ffs_volume_t	*fs;
	ffs_object_t	*obj;
	ffs_file_t		*fp;

	fp	= &FFS_Files[fd];
	obj	= fp->obj;

	if( !ValidateObject( obj ))
		return SetErrNo( ENFILE, -1 );	//FIXED

	if( salt != fp->salt )
		return SetErrNo( ENFILE, -1 );

	fs	= obj->fs;

	if( (ffs_fresult_t)fp->err != FR_OK )
		return SetErrNo( EINVAL, -1 );	//FIXME

	if( !( fp->mode & _FWRITE ))
		return SetErrNo( EACCES, -1 );	//FIXED

	if( len != -1 && _lseek_b( fp, len, SEEK_SET ) < 0 )
		return SetErrNo( EACCES, -1 );	//FIXME

	if( fp->fptr < obj->sizebytes )  	/* Process when fptr is not on the eof */
		{
		ffs_fresult_t	res;

		if( fp->fptr == 0 )  	/* When set file size to zero, remove entire cluster chain */
			{
			res = remove_chain( obj, obj->startcluster, 0 );
			obj->startcluster	= 0;
			obj->sizebytes		= fp->fptr;	/* Set file size to current read/write point */
			obj->statusflags   |= FA_MODIFIED;
			if( res != FR_OK )
				{
				fp->err = (uint8_t)res;
				return SetErrNo( ENFILE, -1 );	//FIXME
				}
			}
		else  				/* When truncate a part of the file, remove remaining clusters */
			{
			uint32_t	ncl;

			ncl = get_fat( obj, fp->currentcluster );
			if( ncl == 0xFFFFFFFF )
				return SetErrNo( ENFILE, -1 );	//FIXME: FR_DISK_ERR
			if( ncl == 1 )
				return SetErrNo( ENFILE, -1 );	//FIXME: FR_INT_ERR
			if( ncl < fs->fatentriescount && ( res = remove_chain( obj, ncl, fp->currentcluster )) != FR_OK )
				{
				obj->sizebytes		= fp->fptr;	/* Set file size to current read/write point */
				obj->statusflags   |= FA_MODIFIED;
				fp->err				= (uint8_t)res;
				return SetErrNo( EINVAL, -1 );	//FIXME
				}

			obj->sizebytes		= fp->fptr;	/* Set file size to current read/write point */
			obj->statusflags   |= FA_MODIFIED;
			}

		if( obj->statusflags & FA_DIRTY )
			{
			if( disk_write( fs->pdrv, obj->buf, obj->sectorinbuf, 1 ) != RES_OK )
				{
				fp->err = (uint8_t)FR_DISK_ERR;
				return SetErrNo( EINVAL, -1 );	//FIXME: FR_DISK_ERR
				}
			obj->statusflags &= (uint8_t)~FA_DIRTY;
			}
		}

	return 0;
	}
/*----------------------------------------------------------------------------*/
int ftruncate64( int fd, ffs_off_t len )
	{
	int	result, salt;

	/* The system mutex is NULL, meaning that the file system was not initialized,
	   so there are no logical drives mounted and there is nothing we can do here... */
	if( FFS_SystemMutex == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	salt	= fd >> 8;
	fd 		= ( fd & 0xff ) - FFS_RESERVED_FILES;

	if( !FileIndexIsValid( fd ))
		return SetErrNo( ENFILE, -1 );

	if( !FFS_TakeMutex( FFS_SystemMutex ))
		return SetErrNo( EBUSY, -1 );

	result	= _truncate_a( fd, salt, len );
	
	FFS_ReleaseMutex( FFS_SystemMutex );

	return result;
	}
/*----------------------------------------------------------------------------*/
int ftruncate( int fd, off_t len )
	{
	return ftruncate64( fd, (ffs_off_t)len );
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Delete a File/Directory                                                    */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static int _unlink_a( const ffs_char_t *ldn, const ffs_char_t *path )
	{
	ffs_volume_t	*fs;
	ffs_directory_t	dj, sdj;
	ffs_object_t	djobj, sdjobj;
	uint32_t		dclst = 0;
	ffs_object_t	obj;

	if(( fs = FindVolumeFromName( ldn )) == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	dj.obj		= &djobj;
	dj.obj->fs	= fs;

	if( follow_path( &dj, path ) != FR_OK )		/* Follow the file path */
		return SetErrNo( ENOENT, -1 );	//FIXED

	if( dj.fn[NSFLAG] & NS_DOT )
		return SetErrNo( ENOENT, -1 );	//FIXME: FR_INVALID_NAME;			/* Cannot remove dot entry */

	/* The file is open... */
	if( ObjectIndexIsValid( SearchObject( &dj )))
		/* ...it cannot be erased. */
		return SetErrNo( EACCES, -1 );	//FIXED
		
	if( dj.fn[NSFLAG] & NS_NONAME )
		return SetErrNo( ENOENT, -1 );	//FIXME: FR_INVALID_NAME;		/* Cannot remove the origin directory */

	if( dj.obj->attributes & FFS_FILEATTRIBUTE_READONLY )
		return SetErrNo( ENOENT, -1 );	//FIXME: FR_DENIED;		/* Cannot remove R/O object */

	obj.fs = fs;
	if( fs->fs_type == FFS_FILESYSTEMTYPE_EXFAT )
		{
		init_alloc_info( fs, &obj );
		dclst = obj.startcluster;
		}
	else
		dclst = ld_clust( fs, dj.dir );

	if( dj.obj->attributes & FFS_FILEATTRIBUTE_DIRECTORY )  			/* Is it a sub-directory? */
		{
		ffs_fresult_t	res;

		if( dclst == fs->curdirstartcluster )  	 	/* Is it the current directory? */
			return SetErrNo( ENOENT, -1 );	//FIXME: FR_DENIED;

		sdj.obj			= &sdjobj;
		sdj.obj->fs		= fs;			/* Open the sub-directory */
		sdj.obj->startcluster	= dclst;
		if( fs->fs_type == FFS_FILESYSTEMTYPE_EXFAT )
			{
			sdj.obj->sizebytes		= obj.sizebytes;
			sdj.obj->chainstatus	= obj.chainstatus;
			}

		if( dir_sdi( &sdj, 0 ) != FR_OK )
			return SetErrNo( ENOENT, -1 );	//FIXME

		if(( res = dir_read_file( &sdj )) != FR_NO_FILE )			/* Test if the directory is empty */
			{
			if( res == FR_OK )
				return SetErrNo( ENOENT, -1 );	//FIXME: FR_DENIED;	/* Not empty? */
			return SetErrNo( ENOENT, -1 );	//FIXME
			}
		}

	if( dir_remove( &dj ) != FR_OK )			/* Remove the directory entry */
		return SetErrNo( ENOENT, -1 );	//FIXME

	if( dclst != 0 && remove_chain( &obj, dclst, 0 ) != FR_OK )  	/* Remove the cluster chain if exist */
		return SetErrNo( ENOENT, -1 );	//FIXME

	if( sync_fs( fs ) != FR_OK )
		return SetErrNo( ENOENT, -1 );	//FIXME

	return 0;
	}
/*----------------------------------------------------------------------------*/
int _unlink( const ffs_char_t *path )		/* Pointer to the file or directory path */
	{
	ffs_char_t	ldn[6];
	int			result;

	/* The system mutex is NULL, meaning that the file system was not initialized,
	   so there are no logical drives mounted and there is nothing we can do here... */
	if( FFS_SystemMutex == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	if( path == NULL )
		return SetErrNo( ENODEV, -1 ); //FIXME

	if( ParseLogicalDriveName( &path, ldn, sizeof ldn ) < 0 )
		return SetErrNo( ENODEV, -1 ); //FIXME

	/* From now on we must have exclusive access to the file system... */
	if( !FFS_TakeMutex( FFS_SystemMutex ))
		/* ...but it was hogged for too long by another task. */
		return SetErrNo( EBUSY, -1 );

	/* Call the function that is going to do the real dirty work. */
	result	= _unlink_a( ldn, path );

	/* Release the mutex, because we don't want to hang the system. */
	FFS_ReleaseMutex( FFS_SystemMutex );

	return result;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Create a Directory                                                    */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static int _mkdir_a( const ffs_char_t *ldn, const ffs_char_t *path )
	{
	ffs_volume_t	*fs;
	ffs_directory_t	dj;
	ffs_object_t	sobj;
	uint32_t		dcl, tm;
	ffs_fresult_t	res;

	if(( fs = FindVolumeFromName( ldn )) == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	dj.obj			= &sobj;
	dj.objectidx	= INVALID_OBJECT_INDEX;
	dj.obj->fs		= fs;

	if(( res = follow_path( &dj, path )) != FR_NO_FILE )			/* Follow the file path */
		{
		if( res == FR_OK )
			return SetErrNo( EEXIST, -1 ); //FIXED					/* Name collision? */
		return SetErrNo( EINVAL, -1 ); //FIXME						/* Name collision? */
		}

	if( dj.fn[NSFLAG] & NS_DOT )								  	/* Invalid name? */
		return SetErrNo( EINVAL, -1 );	//FIXED

	sobj.fs	= fs;						/* New object id to create a new chain */
	dcl		= create_chain( &sobj, 0 );	/* Allocate a cluster for the new directory */

	if( dcl == 0 )
		return SetErrNo( EINVAL, -1 );	//FIXME: FR_DENIED		/* No space to allocate a new cluster? */
	else if( dcl == 1 )
		return SetErrNo( EINVAL, -1 );	//FIXME: FR_INT_ERR		/* Any insanity? */
	else if( dcl == 0xFFFFFFFF )
		return SetErrNo( EINVAL, -1 );	//FIXME: FR_DISK_ERR	/* Disk error? */

	tm = get_fattime();

	if( dir_clear( fs, dcl ) != FR_OK )		/* Clean up the new table */
		return SetErrNo( EINVAL, -1 );	//FIXME

	if( fs->fs_type != FFS_FILESYSTEMTYPE_EXFAT )  	/* Create dot entries (FAT only) */
		{
		uint32_t	pcl;

		memset( fs->win + DIR_Name, ' ', 11 );	/* Create "." entry */
		fs->win[DIR_Name] = '.';
		fs->win[DIR_Attr] = FFS_FILEATTRIBUTE_DIRECTORY;
		st_dword( fs->win + DIR_ModTime, tm );
		st_clust( fs, fs->win, dcl );
		memcpy( fs->win + SIZE_OF_DIR_ENTRY, fs->win, SIZE_OF_DIR_ENTRY );	/* Create ".." entry */
		fs->win[SIZE_OF_DIR_ENTRY + 1] = '.';
		pcl = dj.obj->startcluster;
		st_clust( fs, fs->win + SIZE_OF_DIR_ENTRY, pcl );
		fs->windowisdirty = 1;
		}

	if( dir_register( &dj ) != FR_OK )		/* Register the object to the parent directoy */
		{
		remove_chain( &sobj, dcl, 0 );		/* Could not register, remove the allocated cluster */
		return SetErrNo( EINVAL, -1 ); //FIXME
		}

	if( fs->fs_type == FFS_FILESYSTEMTYPE_EXFAT )  				/* Initialize directory entry block */
		{
		st_dword( fs->dirbuf + XDIR_ModTime, tm );				/* Created time */
		st_dword( fs->dirbuf + XDIR_FstClus, dcl );				/* Table start cluster */
		st_dword( fs->dirbuf + XDIR_FileSize, (uint32_t)fs->sectorspercluster * fs->bytespersector );	/* Directory size needs to be valid */
		st_dword( fs->dirbuf + XDIR_ValidFileSize, (uint32_t)fs->sectorspercluster * fs->bytespersector );
		fs->dirbuf[XDIR_GenFlags]	= 3;						/* Initialize the object flag */
		fs->dirbuf[XDIR_Attr]		= FFS_FILEATTRIBUTE_DIRECTORY;					/* Attribute */
		if( store_xdir( &dj ) != FR_OK )
			return SetErrNo( EINVAL, -1 ); //FIXME
		}
	else
		{
		st_dword( dj.dir + DIR_ModTime, tm );	/* Created time */
		st_clust( fs, dj.dir, dcl );			/* Table start cluster */
		dj.dir[DIR_Attr]	= FFS_FILEATTRIBUTE_DIRECTORY;			/* Attribute */
		fs->windowisdirty			= 1;
		}

	if( sync_fs( fs ) != FR_OK )
		return SetErrNo( EINVAL, -1 ); //FIXME

	return 0;
	}
/*----------------------------------------------------------------------------*/
int _mkdir( const ffs_char_t *path, mode_t mode )	/* Pointer to the directory path */
	{
	ffs_char_t	ldn[6];
	int			result;

	(void)mode;

	/* The system mutex is NULL, meaning that the file system was not initialized,
	   so there are no logical drives mounted and there is nothing we can do here... */
	if( FFS_SystemMutex == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	if( path == NULL )
		return SetErrNo( ENODEV, -1 ); //FIXME

	if( ParseLogicalDriveName( &path, ldn, sizeof ldn ) < 0 )
		return SetErrNo( ENODEV, -1 ); //FIXME

	/* From now on we must have exclusive access to the file system... */
	if( !FFS_TakeMutex( FFS_SystemMutex ))
		/* ...but it was hogged for too long by another task. */
		return SetErrNo( EBUSY, -1 );

	/* Call the function that is going to do the real dirty work. */
	result	= _mkdir_a( ldn, path );

	/* Release the mutex, because we don't want to hang the system. */
	FFS_ReleaseMutex( FFS_SystemMutex );

	return result;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Rename a File/Directory                                               */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static int _rename_a( const ffs_char_t *ldn, const ffs_char_t *path_old, const ffs_char_t *path_new )	/* Pointer to the object name to be renamed */ /* Pointer to the new name */
	{
	ffs_volume_t	*fs;
	ffs_directory_t	djo, djn;
	ffs_object_t	objo, objn;
	uint8_t			buf[SIZE_OF_DIR_ENTRY * 2];
	ffs_fresult_t	res;

	if(( fs = FindVolumeFromName( ldn )) == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	djo.obj		= &objo;
	djo.obj->fs	= fs;

	if( follow_path( &djo, path_old ) != FR_OK )					/* Check old object */
		return SetErrNo( EINVAL, -1 ); //FIXME

	if( djo.fn[NSFLAG] & ( NS_DOT | NS_NONAME ))
		return SetErrNo( EINVAL, -1 ); //FIXME: FR_INVALID_NAME;	/* Check validity of name */

	/* The file is open... */
	if( ObjectIndexIsValid( SearchObject( &djo )))
		/* ...it cannot be renamed. */
		return SetErrNo( EACCES, -1 );
	
	if( fs->fs_type == FFS_FILESYSTEMTYPE_EXFAT )  	/* At exFAT volume */
		{
		uint8_t		nf, nn;
		uint16_t	nh;

		memcpy( buf, fs->dirbuf, SIZE_OF_DIR_ENTRY * 2 );	/* Save 85+C0 entry of old object */
		memcpy( &objn, &objo, sizeof objn );
		memcpy( &djn, &djo, sizeof djn );
		djn.obj	= &objn;
		
		res = follow_path( &djn, path_new );		/* Make sure if new object name is not in use */
		if( res == FR_OK && ( djn.obj->startcluster != djo.obj->startcluster || djn.direntryoffset != djo.direntryoffset ))  						/* Is new name already in use by any other object? */
			return SetErrNo( EEXIST, -1 );	//FIXED

		if( res != FR_NO_FILE )   				/* It is a valid path and no name collision */
			return SetErrNo( EEXIST, -1 );

		if( dir_register( &djn ) != FR_OK )			/* Register the new entry */
			return SetErrNo( EINVAL, -1 );	//FIXME

		nf	= fs->dirbuf[XDIR_NumSec];
		nn	= fs->dirbuf[XDIR_NumName];
		nh	= ld_word( fs->dirbuf + XDIR_NameHash );
		memcpy( fs->dirbuf, buf, SIZE_OF_DIR_ENTRY * 2 );	/* Restore 85+C0 entry */
		fs->dirbuf[XDIR_NumSec]		= nf;
		fs->dirbuf[XDIR_NumName]	= nn;
		st_word( fs->dirbuf + XDIR_NameHash, nh );
		if( !( fs->dirbuf[XDIR_Attr] & FFS_FILEATTRIBUTE_DIRECTORY ))
			fs->dirbuf[XDIR_Attr] |= FFS_FILEATTRIBUTE_ARCHIVE;	/* Set archive attribute if it is a file */
		/* Start of critical section where an interruption can cause a cross-link */
		if( store_xdir( &djn ) != FR_OK )
			return SetErrNo( EINVAL, -1 );	//FIXME
		}
	else
		{
		uint8_t	*dir;

		/* At FAT/FAT32 volume */
		memcpy( buf, djo.dir, SIZE_OF_DIR_ENTRY );			/* Save directory entry of the object */
		memcpy( &objn, &objo, sizeof objn );
		memcpy( &djn, &djo, sizeof djn );		/* Duplicate the directory object */
		djn.obj	= &objn;

		res = follow_path( &djn, path_new );		/* Make sure if new object name is not in use */
		if( res == FR_OK && ( djn.obj->startcluster != djo.obj->startcluster || djn.direntryoffset != djo.direntryoffset ))  						/* Is new name already in use by any other object? */
			return SetErrNo( EEXIST, -1 );	//FIXED

		if( res != FR_NO_FILE )   									/* It is a valid path and no name collision */
			return SetErrNo( EEXIST, -1 );

		if( dir_register( &djn ) != FR_OK )			/* Register the new entry */
			return SetErrNo( EINVAL, -1 );	//FIXME

		dir	= djn.dir;					/* Copy directory entry of the object except name */
		memcpy( dir + 13, buf + 13, SIZE_OF_DIR_ENTRY - 13 );
		dir[DIR_Attr] = buf[DIR_Attr];
		if( !( dir[DIR_Attr] & FFS_FILEATTRIBUTE_DIRECTORY ))
			dir[DIR_Attr] |= FFS_FILEATTRIBUTE_ARCHIVE;	/* Set archive attribute if it is a file */
		fs->windowisdirty = 1;
		if(( dir[DIR_Attr] & FFS_FILEATTRIBUTE_DIRECTORY ) && djo.obj->startcluster != djn.obj->startcluster )  	/* Update .. entry in the sub-directory if needed */
			{
			lba_t	sect;

			if(( sect = clst2sect( fs, ld_clust( fs, dir ))) == 0 )
				return SetErrNo( EINVAL, -1 );	//FIXME: FR_INT_ERR

			/* Start of critical section where an interruption can cause a cross-link */
			if( move_window( fs, sect ) != FR_OK )
				return SetErrNo( EINVAL, -1 );	//FIXME: FR_INT_ERR

			dir = fs->win + SIZE_OF_DIR_ENTRY * 1;	/* Ptr to .. entry */

			if( dir[1] == '.' )
				{
				st_clust( fs, dir, djn.obj->startcluster );
				fs->windowisdirty = 1;
				}
			}
		}

	if( dir_remove( &djo ) != FR_OK )		/* Remove old entry */
		return SetErrNo( EINVAL, -1 );	//FIXME: FR_INT_ERR

	if( sync_fs( fs ) != FR_OK )
		return SetErrNo( EINVAL, -1 );	//FIXME: FR_INT_ERR

	/* End of the critical section */

	return 0;
	}
/*----------------------------------------------------------------------------*/
int _rename( const ffs_char_t *path_old, const ffs_char_t *path_new )	/* Pointer to the object name to be renamed */ /* Pointer to the new name */
	{
	ffs_char_t	ldn_old[6], ldn_new[6];
	int			result;

	/* The system mutex is NULL, meaning that the file system was not initialized,
	   so there are no logical drives mounted and there is nothing we can do here... */
	if( FFS_SystemMutex == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	if( path_old == NULL || path_new == NULL )
		return SetErrNo( ENODEV, -1 ); //FIXME

	if( ParseLogicalDriveName( &path_old, ldn_old, sizeof ldn_old ) < 0 )
		return SetErrNo( ENODEV, -1 ); //FIXME

	if( ParseLogicalDriveName( &path_new, ldn_new, sizeof ldn_new ) < 0 )
		return SetErrNo( ENODEV, -1 ); //FIXME

    //strupr( ldn_old );
	char *p;
    for( p = ldn_old; *p != '\0'; p++ )
        *p  = toupper( *p );
    //strupr( ldn_new );
    for( p = ldn_new; *p != '\0'; p++ )
        *p  = toupper( *p );

	/* The new path includes a drive name and it is not the same as the old path's... */
	if( ldn_new[0] != '\0' && strcmp( ldn_old, ldn_new ) != 0 )
		return SetErrNo( ENODEV, -1 ); //FIXME

	/* From now on we must have exclusive access to the file system... */
	if( !FFS_TakeMutex( FFS_SystemMutex ))
		/* ...but it was hogged for too long by another task. */
		return SetErrNo( EBUSY, -1 );

	/* Call the function that is going to do the real dirty work. */
	result	= _rename_a( ldn_old, path_old, path_new );

	/* Release the mutex, because we don't want to hang the system. */
	FFS_ReleaseMutex( FFS_SystemMutex );

	return result;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Change Attribute                                                      */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static int _chmod_a( const ffs_char_t *ldn, const ffs_char_t *path, uint8_t attr, uint8_t mask )	/* Pointer to the file path */ /* Attribute bits */ /* Attribute mask to change */
	{
	ffs_volume_t	*fs;
	ffs_directory_t	dj;
	ffs_object_t	djobj;

	if(( fs = FindVolumeFromName( ldn )) == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	dj.obj			= &djobj;
	dj.objectidx	= INVALID_OBJECT_INDEX;
	dj.obj->fs		= fs;

	if( follow_path( &dj, path ) != FR_OK )	/* Follow the file path */
		return SetErrNo( ENOENT, -1 );

	if( dj.fn[NSFLAG] & ( NS_DOT | NS_NONAME ))
		return SetErrNo( EINVAL, -1 );	//FIXME: FR_INVALID_NAME	/* Check object validity */

	mask &= FFS_FILEATTRIBUTE_READONLY | FFS_FILEATTRIBUTE_HIDDEN | FFS_FILEATTRIBUTE_SYSTEM | FFS_FILEATTRIBUTE_ARCHIVE;	/* Valid attribute mask */

	if( fs->fs_type == FFS_FILESYSTEMTYPE_EXFAT )
		{
		fs->dirbuf[XDIR_Attr] = ( attr & mask ) | ( fs->dirbuf[XDIR_Attr] & (uint8_t)~mask );	/* Apply attribute change */
		if( store_xdir( &dj ) != FR_OK )
			return SetErrNo( EINVAL, -1 );	//FIXME
		}
	else
		{
		dj.dir[DIR_Attr] = ( attr & mask ) | ( dj.dir[DIR_Attr] & (uint8_t)~mask );	/* Apply attribute change */
		fs->windowisdirty = 1;
		}

	if( sync_fs( fs ) != FR_OK )
		return SetErrNo( EINVAL, -1 );	//FIXME

	return 0;
	}
/*----------------------------------------------------------------------------*/
int chmod( const ffs_char_t *path, uint32_t attr )	/* Pointer to the file path */ /* Attribute bits */ /* Attribute mask to change */
	{
	ffs_char_t	ldn[6];
	int			result;

	/* The system mutex is NULL, meaning that the file system was not initialized,
	   so there are no logical drives mounted and there is nothing we can do here... */
	if( FFS_SystemMutex == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	if( path == NULL )
		return SetErrNo( ENODEV, -1 ); //FIXME

	if( ParseLogicalDriveName( &path, ldn, sizeof ldn ) < 0 )
		return SetErrNo( ENODEV, -1 ); //FIXME

	/* From now on we must have exclusive access to the file system... */
	if( !FFS_TakeMutex( FFS_SystemMutex ))
		/* ...but it was hogged for too long by another task. */
		return SetErrNo( EBUSY, -1 );

	/* Call the function that is going to do the real dirty work. */
	result	= _chmod_a( ldn, path, attr, -1 );

	/* Release the mutex, because we don't want to hang the system. */
	FFS_ReleaseMutex( FFS_SystemMutex );

	return result;
	}
/*----------------------------------------------------------------------------*/
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Change Timestamp                                                      */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static int _utime_a( const ffs_char_t *ldn, const ffs_char_t *path, const ffs_fileinfo_t *fno )	/* Pointer to the file/directory name */ /* Pointer to the time stamp to be set */
	{
	ffs_volume_t	*fs;
	ffs_directory_t	dj;
	ffs_object_t	djobj;

	if(( fs = FindVolumeFromName( ldn )) == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	dj.obj			= &djobj;
	dj.objectidx	= INVALID_OBJECT_INDEX;
	dj.obj->fs		= fs;

	if( follow_path( &dj, path ) != FR_OK )		/* Follow the file path */
		return SetErrNo( ENOENT, -1 ); //FIXED

	if( dj.fn[NSFLAG] & ( NS_DOT | NS_NONAME ))	/* Check object validity */
		return SetErrNo( EINVAL, -1 ); //FIXED

	if( fs->fs_type == FFS_FILESYSTEMTYPE_EXFAT )
		{
		st_dword( fs->dirbuf + XDIR_ModTime, (uint32_t)fno->fdate << 16 | fno->ftime );
		if( store_xdir( &dj ) != FR_OK )
			return SetErrNo( EINVAL, -1 ); //FIXME
		}
	else
		{
		st_dword( dj.dir + DIR_ModTime, (uint32_t)fno->fdate << 16 | fno->ftime );
		fs->windowisdirty = 1;
		}

	if( sync_fs( fs ) != FR_OK )
		return SetErrNo( EINVAL, -1 ); //FIXME

	return 0;
	}
/*----------------------------------------------------------------------------*/
int utime( const ffs_char_t *path, const ffs_fileinfo_t *fno )
	{
	ffs_char_t	ldn[6];
	int			result;

	/* The system mutex is NULL, meaning that the file system was not initialized,
	   so there are no logical drives mounted and there is nothing we can do here... */
	if( FFS_SystemMutex == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	if( path == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	/* This operation is safe to do before acquiring the mutex because it only
	   uses local data.*/
	if( ParseLogicalDriveName( &path, ldn, sizeof ldn ) < 0 )
		return SetErrNo( ENODEV, -1 ); //FIXME

	if( !FFS_TakeMutex( FFS_SystemMutex ))
		return SetErrNo( EBUSY, -1 );

	result	= _utime_a( ldn, path, fno );
	
	FFS_ReleaseMutex( FFS_SystemMutex );

	return result;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Get Volume Label                                                      */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static int _getlabel_a( const ffs_char_t *ldn, ffs_char_t *label, uint32_t *vsn ) 	/* Path name of the logical drive number */ /* Pointer to a buffer to return the volume label */ /* Pointer to a variable to return the volume serial number */
	{
	ffs_fresult_t	res;
	ffs_volume_t	*fs;
	unsigned int	di;

	/* Get logical drive */
	if(( fs = FindVolumeFromName( ldn )) == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	/* Get volume label */
	if( label != NULL )
		{
		ffs_directory_t	dj;
		ffs_object_t	djobj;
		unsigned int	si;

		dj.obj			= &djobj;
		dj.objectidx	= -1;
		dj.obj->fs		= fs;
		dj.obj->startcluster	= 0;	/* Open root directory */

		if(( res = dir_sdi( &dj, 0 )) == FR_NO_FILE )
			label[0] = 0;
		else if( res != FR_OK )
			return SetErrNo( EINVAL, -1 ); //FIXME
		else
			{
			ffs_wchar_t	wc;

			if( dir_read_label( &dj ) != FR_OK )		/* Find a volume label entry */
				return SetErrNo( EINVAL, -1 ); //FIXME

			if( fs->fs_type == FFS_FILESYSTEMTYPE_EXFAT )
				{
				ffs_wchar_t		hs;
				unsigned int	nw;

				for( si = di = hs = 0; si < dj.dir[XDIR_NumLabel]; si++ )  	/* Extract volume label from 83 entry */
					{
					wc = ld_word( dj.dir + XDIR_Label + si * 2 );
					if( hs == 0 && IsSurrogate( wc ))  	/* Is the code a surrogate? */
						{
						hs = wc;
						continue;
						}
					nw = put_utf((uint32_t)hs << 16 | wc, &label[di], 4 );	/* Store it in API encoding */
					if( nw == 0 )  		/* Encode error? */
						{
						di = 0;
						break;
						}
					di += nw;
					hs = 0;
					}
				if( hs != 0 )
					di = 0;	/* Broken surrogate pair? */
				label[di] = 0;
				}
			else
				{
				si = di = 0;		/* Extract volume label from AM_VOL entry */
				while( si < 11 )
					{
					wc = dj.dir[si++];
#if FFS_CONFIG_LFNUNICODE >= 1 	/* Unicode output */
					if( dbc_1st((uint8_t)wc ) && si < 11 )
						wc = wc << 8 | dj.dir[si++];	/* Is it a DBC? */
					wc = ffs_oem2uni( wc, FFS_CodePage );		/* Convert it into Unicode */
					if( wc == 0 )  		/* Invalid char in current code page? */
						{
						di = 0;
						break;
						}
					di += put_utf( wc, &label[di], 4 );	/* Store it in Unicode */
#else									/* ANSI/OEM output */
					label[di++] = (ffs_char_t)wc;
#endif
					}
				do  				/* Truncate trailing spaces */
					{
					label[di] = 0;
					if( di == 0 )
						break;
					}
				while( label[--di] == ' ' );
				}
			}
		}

	/* Get volume serial number */
	if( vsn != NULL )
		{
		if( move_window( fs, fs->volbase ) != FR_OK )
			return SetErrNo( EINVAL, -1 ); //FIXME

		switch( fs->fs_type )
			{
			case FFS_FILESYSTEMTYPE_EXFAT:
				di = BPB_VolIDEx;
				break;

			case FFS_FILESYSTEMTYPE_FAT32:
				di = BS_VolID32;
				break;

			default:
				di = BS_VolID;
			}
		*vsn = ld_dword( fs->win + di );
		}

	return 0;
	}
/*----------------------------------------------------------------------------*/
int getlabel( const ffs_char_t *ldname, ffs_char_t *label, uint32_t *vsn ) 	/* Path name of the logical drive number */ /* Pointer to a buffer to return the volume label */ /* Pointer to a variable to return the volume serial number */
	{
	ffs_char_t	ldn[6];
	int			result;

	/* The system mutex is NULL, meaning that the file system was not initialized,
	   so there are no logical drives mounted and there is nothing we can do here... */
	if( FFS_SystemMutex == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	if( ldname == NULL && label == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	/* This operation is safe to do before acquiring the mutex because it only
	   uses local data.*/
	if( ParseLogicalDriveName( &ldname, ldn, sizeof ldn ) <= 0 )
		return SetErrNo( ENODEV, -1 ); //FIXME

	/* There are extra characters after the logical volume name... */
	if( *ldname != '\0' )
		return SetErrNo( ENODEV, -1 ); //FIXME

	if( !FFS_TakeMutex( FFS_SystemMutex ))
		return SetErrNo( EBUSY, -1 );

	result	= _getlabel_a( ldn, label, vsn );
	
	FFS_ReleaseMutex( FFS_SystemMutex );

	return result;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Set Volume Label                                                      */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
static int _setlabel_a( const ffs_char_t *ldn, const ffs_char_t *label )
	{
	static const char	badchr[18] = "+.,;=[]" "/*:<>|\\\"\?\x7F";	/* [0..16] for FAT, [7..16] for exFAT */
	ffs_volume_t		*fs;
	uint32_t			dc;
	ffs_directory_t		dj;
	ffs_object_t		djobj;
	unsigned int		di;
	uint8_t				dirvn[22];
	ffs_fresult_t		res;

	/* Get logical drive */
	if(( fs = FindVolumeFromName( ldn )) == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	if( fs->fs_type == FFS_FILESYSTEMTYPE_EXFAT )  	/* On the exFAT volume */
		{
		memset( dirvn, 0, 22 );
		di = 0;
		while((unsigned int)*label >= ' ' )  	/* Create volume label */
			{
			dc = tchar_t2uni( &label );	/* Get a Unicode character */
			if( dc >= 0x10000 )
				{
				if( dc == 0xFFFFFFFF || di >= 10 )  	/* Wrong surrogate or buffer overflow */
					dc = 0;
				else
					{
					st_word( dirvn + di * 2, (ffs_wchar_t)( dc >> 16 ));
					di++;
					}
				}
			if( dc == 0 || strchr( &badchr[7], (int)dc ) || di >= 11 )  	/* Check validity of the volume label */
				return SetErrNo( EINVAL, -1 ); //FIXME: FR_INVALID_NAME
			st_word( dirvn + di * 2, (ffs_wchar_t)dc );
			di++;
			}
		}
	else
		{
		/* On the FAT/FAT32 volume */
		memset( dirvn, ' ', 11 );
		di = 0;
		while((unsigned int)*label >= ' ' )  	/* Create volume label */
			{
			ffs_wchar_t	wc;

			dc = tchar_t2uni( &label );
			wc = ( dc < 0x10000 ) ? ffs_uni2oem( ffs_wtoupper( dc ), FFS_CodePage ) : 0;
			if( wc == 0 || strchr( &badchr[0], (int)wc ) || di >= (unsigned int)(( wc >= 0x100 ) ? 10 : 11 ))  	/* Reject invalid characters for volume label */
				return SetErrNo( EINVAL, -1 ); //FIXME: FR_INVALID_NAME
			if( wc >= 0x100 )
				dirvn[di++] = (uint8_t)( wc >> 8 );
			dirvn[di++] = (uint8_t)wc;
			}
		if( dirvn[0] == DDEM )
			return SetErrNo( EINVAL, -1 ); //FIXME: FR_INVALID_NAME
		while( di && dirvn[di - 1] == ' ' )
			di--;				/* Snip trailing spaces */
		}

	/* Set volume label */
	dj.obj			= &djobj;
	dj.objectidx	= INVALID_OBJECT_INDEX;
	dj.obj->fs		= fs;
	dj.obj->startcluster	= 0;	/* Open root directory */
	if( dir_sdi( &dj, 0 ) != FR_OK )
		return SetErrNo( EINVAL, -1 ); //FIXME

	res = dir_read_label( &dj );	/* Get volume label entry */
	if( res != FR_OK && res != FR_NO_FILE )
		return SetErrNo( EINVAL, -1 ); //FIXME

	if( res == FR_OK )
		{
		if( fs->fs_type == FFS_FILESYSTEMTYPE_EXFAT )
			{
			dj.dir[XDIR_NumLabel] = (uint8_t)di;	/* Change the volume label */
			memcpy( dj.dir + XDIR_Label, dirvn, 22 );
			}
		else
			{
			if( di != 0 )
				memcpy( dj.dir, dirvn, 11 );	/* Change the volume label */
			else
				dj.dir[DIR_Name] = DDEM;	/* Remove the volume label */
			}
		}
	else if( di != 0 )  	/* No volume label entry or an error *//* Create a volume label entry */
		{
		if( dir_alloc( &dj, 1 ) != FR_OK )	/* Allocate an entry */
			return SetErrNo( EINVAL, -1 ); //FIXME

		memset( dj.dir, 0, SIZE_OF_DIR_ENTRY );	/* Clean the entry */
		if( fs->fs_type == FFS_FILESYSTEMTYPE_EXFAT )
			{
			dj.dir[XDIR_Type] = ET_VLABEL;	/* Create volume label entry */
			dj.dir[XDIR_NumLabel] = (uint8_t)di;
			memcpy( dj.dir + XDIR_Label, dirvn, 22 );
			}
		else
			{
			dj.dir[DIR_Attr] = AM_VOL;		/* Create volume label entry */
			memcpy( dj.dir, dirvn, 11 );
			}
		}

	fs->windowisdirty = 1;
	if( sync_fs( fs ) != FR_OK )
		return SetErrNo( EINVAL, -1 ); //FIXME

	return 0;
	}
/*----------------------------------------------------------------------------*/
int setlabel( const ffs_char_t *label )	/* Pointer to the volume label to set */
	{
	ffs_char_t	ldn[6];
	int			result;

	/* The system mutex is NULL, meaning that the file system was not initialized,
	   so there are no logical drives mounted and there is nothing we can do here... */
	if( FFS_SystemMutex == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	if( label == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	/* This operation is safe to do before acquiring the mutex because it only
	   uses local data.*/
	if( ParseLogicalDriveName( &label, ldn, sizeof ldn ) <= 0 )
		return SetErrNo( ENODEV, -1 ); //FIXME

	if( !FFS_TakeMutex( FFS_SystemMutex ))
		return SetErrNo( EBUSY, -1 );

	result	= _setlabel_a( ldn, label );
	
	FFS_ReleaseMutex( FFS_SystemMutex );

	return result;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Allocate a Contiguous Blocks to the File                              */
/*----------------------------------------------------------------------------*/
/*============================================================================*/

/* Pointer to the file object */
/* File size to be expanded to */
/* Operation mode 0:Find and prepare or 1:Find and allocate */

static int _expand_a( int fd, int salt, off_t fsz, uint8_t opt ) /* Pointer to the file object */ /* File size to be expanded to */ /* Operation mode 0:Find and prepare or 1:Find and allocate */
	{
	ffs_volume_t	*fs;
	ffs_object_t	*obj;
	ffs_file_t		*fp;
	uint32_t		n, stcl, scl, tcl, lclst;

	fp	= &FFS_Files[fd];
	obj	= fp->obj;

	if( !ValidateObject( obj ))
		return SetErrNo( ENFILE, -1 );

	if( salt != fp->salt )
		return SetErrNo( ENFILE, -1 );

	fs	= obj->fs;

	if(( disk_status( fs->pdrv ) & STA_NOINIT ))
		return SetErrNo( ENFILE, -1 );	//FIXME: FR_DISK_ERR

	if( (ffs_fresult_t)fp->err != FR_OK )
		return SetErrNo( ENFILE, -1 );

	if( fsz == 0 || obj->sizebytes != 0 || !( fp->mode & _FWRITE ))
		return SetErrNo( ENFILE, -1 );	//FIXME: FR_DENIED

	if( fs->fs_type != FFS_FILESYSTEMTYPE_EXFAT && fsz >= 0x100000000 )
		return SetErrNo( ENFILE, -1 );	//FIXME: FR_DENIED

	n		= (uint32_t)fs->sectorspercluster * fs->bytespersector;	/* Cluster size */
	tcl		= (uint32_t)( fsz / n ) + (( fsz & ( n - 1 )) ? 1 : 0 );	/* Number of clusters required */
	stcl	= fs->last_clst;
	lclst	= 0;
	if( stcl < FIRST_VALID_CLUSTER || stcl >= fs->fatentriescount )
		stcl = FIRST_VALID_CLUSTER;

	if( fs->fs_type == FFS_FILESYSTEMTYPE_EXFAT )
		{
		scl = find_bitmap( fs, stcl, tcl );			/* Find a contiguous cluster block */
		if( scl == 0 )
			return SetErrNo( ENFILE, -1 );	//FIXME: FR_DENIED				/* No contiguous cluster block was found */
		if( scl == 0xFFFFFFFF )
			return SetErrNo( ENFILE, -1 );	//FIXME: FR_DISK_ERR

		if( opt )  		/* Allocate it now */
			{
			if( change_bitmap( fs, scl, tcl, 1 ) != FR_OK )	/* Mark the cluster block 'in use' */
				return SetErrNo( ENFILE, -1 );	//FIXME
			lclst = scl + tcl - 1;
			}
		else  		/* Set it as suggested point for next allocation */
			lclst = scl - 1;
		}
	else
		{
		uint32_t	clst, ncl;

		scl = clst = stcl;
		ncl = 0;
		while( 1 )  	/* Find a contiguous cluster block */
			{
			n = get_fat( obj, clst );
			if( ++clst >= fs->fatentriescount )
				clst = FIRST_VALID_CLUSTER;
			if( n == 1 )
				return SetErrNo( ENFILE, -1 );	//FIXME: FR_INT_ERR
			if( n == 0xFFFFFFFF )
				return SetErrNo( ENFILE, -1 );	//FIXME: FR_DISK_ERR
			if( n == 0 )  	/* Is it a free cluster? */
				{
				if( ++ncl == tcl )
					break;	/* Break if a contiguous cluster block is found */
				}
			else
				{
				scl = clst;
				ncl = 0;		/* Not a free cluster */
				}

			if( clst == stcl )  		/* No contiguous cluster? */
				return SetErrNo( ENFILE, -1 );	//FIXME: FR_DENIED
			}

		if( opt )  		/* Allocate it now */
			{
			for( clst = scl, n = tcl; n; clst++, n-- )  	/* Create a cluster chain on the FAT */
				{
				if( put_fat( fs, clst, ( n == 1 ) ? 0xFFFFFFFF : clst + 1 ) != FR_OK )
					return SetErrNo( ENFILE, -1 );	//FIXME
				lclst = clst;
				}
			}
		else  		/* Set it as suggested point for next allocation */
			lclst = scl - 1;
		}

	fs->last_clst = lclst;		/* Set suggested start cluster to start next */
	if( opt )  	/* Is it allocated now? */
		{
		obj->startcluster	= scl;							/* Update object allocation information */
		obj->sizebytes		= fsz;
		obj->chainstatus	= FFS_CHAINSTATUS_NOFATCHAIN;	/* Set status 'contiguous chain' */
		obj->statusflags   |= FA_MODIFIED;
		if( fs->freeclusterscount <= fs->fatentriescount - FIRST_VALID_CLUSTER )  	/* Update FSINFO */
			{
			fs->freeclusterscount  -= tcl;
			fs->fsi_flag		   |= 1;
			}
		}

	return 0;
	}
/*----------------------------------------------------------------------------*/
int expand( int fd, ffs_off_t fsz, int opt )
	{
	int	result, salt;

	/* The system mutex is NULL, meaning that the file system was not initialized,
	   so there are no logical drives mounted and there is nothing we can do here... */
	if( FFS_SystemMutex == NULL )
		return SetErrNo( EINVAL, -1 ); //FIXME

	salt	= fd >> 8;
	fd 		= ( fd & 0xff ) - FFS_RESERVED_FILES;

	if( !FileIndexIsValid( fd ))
		return SetErrNo( ENFILE, -1 );

	if( !FFS_TakeMutex( FFS_SystemMutex ))
		return SetErrNo( EBUSY, -1 );

	result	= _expand_a( fd, salt, fsz, opt );

	FFS_ReleaseMutex( FFS_SystemMutex );

	return result;
	}
/*----------------------------------------------------------------------------*/
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Create FAT/exFAT volume (with sub-functions)                          */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
#define N_SEC_TRACK 63			/* Sectors per track for determination of drive CHS */
#define	GPT_ALIGN	0x100000	/* Alignment of partitions in GPT [byte] (>=128KB) */
#define GPT_ITEMS	128			/* Number of GPT table size (>=128, sector aligned) */
/*============================================================================*/
/* Create partitions on the physical drive in format of MBR or GPT */
/*============================================================================*/
static int create_partition(
	uint8_t		drv,			/* Physical drive number */
	const lba_t	plst[],	/* Partition list */
	uint8_t		sys,			/* System ID for each partition (for only MBR) */
	uint8_t		*buf			/* Working buffer for a sector */
)
	{
	lba_t			sz_drv;
	unsigned int	i;

	/* Get physical drive size */
	if( disk_ioctl( drv, GET_SECTOR_COUNT, &sz_drv ) != RES_OK )
		return SetErrNo( ENODEV, -1 );	//FIXME: FR_DISK_ERR

	if( sz_drv >= FFS_CONFIG_MINGPT )  	/* Create partitions in GPT format */
		{
		static const uint8_t	gpt_mbr[16] = {0x00, 0x00, 0x02, 0x00, 0xEE, 0xFE, 0xFF, 0x00, 0x01, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF};
		uint64_t				nxt_alloc, sz_part, sz_pool, top_bpt;
		uint32_t				bcc, rnd, align;
		unsigned int			sz_ptbl, pi, si, ofs;
		uint16_t				ss;

		if( disk_ioctl( drv, GET_SECTOR_SIZE, &ss ) != RES_OK )
			return SetErrNo( ENODEV, -1 );	//FIXME: FR_DISK_ERR;	/* Get sector size */

		if( ss > FFS_CONFIG_MAXIMUMSECTORSIZE || ss < FFS_CONFIG_MINIMUMSECTORSIZE || ( ss & ( ss - 1 )))
			return SetErrNo( ENODEV, -1 );	//FIXME: FR_DISK_ERR;

		rnd			= (uint32_t)sz_drv + get_fattime();	/* Random seed */
		align		= GPT_ALIGN / ss;				/* Partition alignment for GPT [sector] */
		sz_ptbl		= GPT_ITEMS * SZ_GPTE / ss;	/* Size of partition table [sector] */
		top_bpt		= sz_drv - sz_ptbl - 1;		/* Backup partition table start sector */
		nxt_alloc	= 2 + sz_ptbl;			/* First allocatable sector */
		sz_pool		= top_bpt - nxt_alloc;		/* Size of allocatable area */
		bcc			= 0xFFFFFFFF;
		sz_part		= 1;
		pi = si		= 0;	/* partition table index, size table index */
		do
			{
			if( pi * SZ_GPTE % ss == 0 )
				memset( buf, 0, ss );	/* Clean the buffer if needed */
			if( sz_part != 0 )  				/* Is the size table not termintated? */
				{
				nxt_alloc	= ( nxt_alloc + align - 1 ) & ((uint64_t)0 - align );	/* Align partition start */
				sz_part		= plst[si++];		/* Get a partition size */
				if( sz_part <= 100 )  		/* Is the size in percentage? */
					{
					sz_part	= sz_pool * sz_part / 100;
					sz_part	= ( sz_part + align - 1 ) & ((uint64_t)0 - align );	/* Align partition end (only if in percentage) */
					}
				if( nxt_alloc + sz_part > top_bpt )  	/* Clip the size at end of the pool */
					sz_part	= ( nxt_alloc < top_bpt ) ? top_bpt - nxt_alloc : 0;
				}
			if( sz_part != 0 )  				/* Add a partition? */
				{
				ofs		= pi * SZ_GPTE % ss;
				memcpy( buf + ofs + GPTE_PtGuid, GUID_MS_Basic, 16 );	/* Set partition GUID (Microsoft Basic Data) */
				rnd		= make_rand( rnd, buf + ofs + GPTE_UpGuid, 16 );	/* Set unique partition GUID */
				st_qword( buf + ofs + GPTE_FstLba, nxt_alloc );		/* Set partition start sector */
				st_qword( buf + ofs + GPTE_LstLba, nxt_alloc + sz_part - 1 );	/* Set partition end sector */
				nxt_alloc += sz_part;								/* Next allocatable sector */
				}
			if(( pi + 1 ) * SZ_GPTE % ss == 0 )  		/* Write the buffer if it is filled up */
				{
				for( i = 0; i < ss; bcc = crc32( bcc, buf[i++] ))	/* Calculate table check sum */
					{}
				if( disk_write( drv, buf, 2 + pi * SZ_GPTE / ss, 1 ) != RES_OK )
					return SetErrNo( ENODEV, -1 );	//FIXME: FR_DISK_ERR;		/* Write to primary table */
				if( disk_write( drv, buf, top_bpt + pi * SZ_GPTE / ss, 1 ) != RES_OK )
					return SetErrNo( ENODEV, -1 );	//FIXME: FR_DISK_ERR;	/* Write to secondary table */
				}
			}
		while( ++pi < GPT_ITEMS );

		/* Create primary GPT header */
		memset( buf, 0, ss );
		memcpy( buf + GPTH_Sign, "EFI PART" "\0\0\1\0" "\x5C\0\0", 16 );	/* Signature, version (1.0) and size (92) */
		st_dword( buf + GPTH_PtBcc, ~bcc );			/* Table check sum */
		st_qword( buf + GPTH_CurLba, 1 );				/* LBA of this header */
		st_qword( buf + GPTH_BakLba, sz_drv - 1 );	/* LBA of secondary header */
		st_qword( buf + GPTH_FstLba, 2 + sz_ptbl );	/* LBA of first allocatable sector */
		st_qword( buf + GPTH_LstLba, top_bpt - 1 );	/* LBA of last allocatable sector */
		st_dword( buf + GPTH_PteSize, SZ_GPTE );		/* Size of a table entry */
		st_dword( buf + GPTH_PtNum, GPT_ITEMS );		/* Number of table entries */
		st_dword( buf + GPTH_PtOfs, 2 );				/* LBA of this table */
		/*rnd =*/ make_rand( rnd, buf + GPTH_DskGuid, 16 );	/* Disk GUID */
		for( i = 0, bcc = 0xFFFFFFFF; i < 92; bcc = crc32( bcc, buf[i++] ))	/* Calculate header check sum */
			{}
		st_dword( buf + GPTH_Bcc, ~bcc );				/* Header check sum */
		if( disk_write( drv, buf, 1, 1 ) != RES_OK )
			return SetErrNo( ENODEV, -1 );	//FIXME: FR_DISK_ERR;

		/* Create secondary GPT header */
		st_qword( buf + GPTH_CurLba, sz_drv - 1 );	/* LBA of this header */
		st_qword( buf + GPTH_BakLba, 1 );				/* LBA of primary header */
		st_qword( buf + GPTH_PtOfs, top_bpt );		/* LBA of this table */
		st_dword( buf + GPTH_Bcc, 0 );
		for( i = 0, bcc = 0xFFFFFFFF; i < 92; bcc = crc32( bcc, buf[i++] ))	/* Calculate header check sum */
			{}
		st_dword( buf + GPTH_Bcc, ~bcc );				/* Header check sum */
		if( disk_write( drv, buf, sz_drv - 1, 1 ) != RES_OK )
			return SetErrNo( ENODEV, -1 );	//FIXME: FR_DISK_ERR;

		/* Create protective MBR */
		memset( buf, 0, ss );
		memcpy( buf + MBR_Table, gpt_mbr, 16 );		/* Create a GPT partition */
		st_word( buf + BS_55AA, 0xAA55 );
		if( disk_write( drv, buf, 0, 1 ) != RES_OK )
			return SetErrNo( ENODEV, -1 );	//FIXME: FR_DISK_ERR;
		}
	else
		{
		uint32_t		sz_drv32, nxt_alloc32, sz_part32;
		uint8_t			n_hd, n_sc;
		uint8_t			*pte;

		/* Create partitions in MBR format */
		sz_drv32	= (uint32_t)sz_drv;
		n_sc		= N_SEC_TRACK;				/* Determine drive CHS without any consideration of the drive geometry */
		for( n_hd = 8; n_hd != 0 && sz_drv32 / n_hd / n_sc > 1024; n_hd *= 2 )
			{}
		if( n_hd == 0 )
			n_hd	= 255;		/* Number of heads needs to be <256 */

		memset( buf, 0, FFS_CONFIG_MAXIMUMSECTORSIZE );		/* Clear MBR */
		pte	= buf + MBR_Table;	/* Partition table in the MBR */
		for( i = 0, nxt_alloc32 = n_sc; i < 4 && nxt_alloc32 != 0 && nxt_alloc32 < sz_drv32; i++, nxt_alloc32 += sz_part32 )
			{
			unsigned int	cy;
			uint8_t			hd, sc;

			sz_part32	= (uint32_t)plst[i];	/* Get partition size */
			if( sz_part32 <= 100 )
				sz_part32 = ( sz_part32 == 100 ) ? sz_drv32 : sz_drv32 / 100 * sz_part32;	/* Size in percentage? */
			if( nxt_alloc32 + sz_part32 > sz_drv32 || nxt_alloc32 + sz_part32 < nxt_alloc32 )
				sz_part32 = sz_drv32 - nxt_alloc32;	/* Clip at drive size */
			if( sz_part32 == 0 )
				break;	/* End of table or no sector to allocate? */

			st_dword( pte + PTE_StLba, nxt_alloc32 );	/* Start LBA */
			st_dword( pte + PTE_SizLba, sz_part32 );	/* Number of sectors */
			pte[PTE_System] = sys;					/* System type */

			cy = (unsigned int)( nxt_alloc32 / n_sc / n_hd );	/* Start cylinder */
			hd = (uint8_t)( nxt_alloc32 / n_sc % n_hd );	/* Start head */
			sc = (uint8_t)( nxt_alloc32 % n_sc + 1 );	/* Start sector */
			pte[PTE_StHead] = hd;
			pte[PTE_StSec] = (uint8_t)(( cy >> 2 & 0xC0 ) | sc );
			pte[PTE_StCyl] = (uint8_t)cy;

			cy = (unsigned int)(( nxt_alloc32 + sz_part32 - 1 ) / n_sc / n_hd );	/* End cylinder */
			hd = (uint8_t)(( nxt_alloc32 + sz_part32 - 1 ) / n_sc % n_hd );	/* End head */
			sc = (uint8_t)(( nxt_alloc32 + sz_part32 - 1 ) % n_sc + 1 );		/* End sector */
			pte[PTE_EdHead] = hd;
			pte[PTE_EdSec] = (uint8_t)(( cy >> 2 & 0xC0 ) | sc );
			pte[PTE_EdCyl] = (uint8_t)cy;

			pte += SZ_PTE;		/* Next entry */
			}

		st_word( buf + BS_55AA, 0xAA55 );		/* MBR signature */
		if( disk_write( drv, buf, 0, 1 ) != RES_OK )
			return SetErrNo( ENODEV, -1 );	//FIXME: FR_DISK_ERR;	/* Write it to the MBR */
		}

	return 0;
	}
/*============================================================================*/
#define	FFS_SYSTYPE_EXFAT		0x07
#define	FFS_SYSTYPE_FAT32X		0x0C
#define	FFS_SYSTYPE_FAT1216L	0x06
#define	FFS_SYSTYPE_FAT16		0x04
#define	FFS_SYSTYPE_FAT12		0x01
/*============================================================================*/
typedef struct
	{
	lba_t			sz_vol;
	lba_t			b_vol;
	uint8_t			*buf;
	uint32_t		sz_buf;
	uint32_t		sz_blk;
	uint32_t		vsn;
	uint32_t		sz_au;
	unsigned int	fatcopies;
	unsigned int	rootentries;
	uint16_t		ss;
	int				pdrv;
	} formatparams_t;
/*============================================================================*/
static int FormatEXFAT( formatparams_t *params )
	{
	uint32_t		szb_bit, szb_case, sum, nbit, clu, clen[3];
	ffs_wchar_t		ch, si;
	unsigned int	j, st;
	lba_t			lba[2];

	lba_t			b_fat;
	lba_t			b_data;
	lba_t			sect;
	uint32_t		sz_fat;
	uint32_t		nsect;
	uint32_t		n_clst;
	uint32_t		n;
	unsigned int	i;


	if( params->sz_vol < 0x1000 )
		return FR_MKFS_ABORTED;	/* Too small volume for exFAT? */
	lba[0] = params->b_vol;
	lba[1] = params->b_vol + params->sz_vol - 1;	/* Inform storage device that the volume area may be erased */
	disk_ioctl( params->pdrv, CTRL_TRIM, lba );
	/* Determine FAT location, data location and number of clusters */
	if( params->sz_au == 0 )  	/* AU auto-selection */
		{
		params->sz_au = 8;
		if( params->sz_vol >= 0x80000 )
			params->sz_au = 64;		/* >= 512Ks */
		if( params->sz_vol >= 0x4000000 )
			params->sz_au = 256;	/* >= 64Ms */
		}
	b_fat = params->b_vol + 32;										/* FAT start at offset 32 */
	sz_fat = (uint32_t)(( params->sz_vol / params->sz_au + FIRST_VALID_CLUSTER ) * 4 + params->ss - 1 ) / params->ss;	/* Number of FAT sectors */
	b_data = ( b_fat + sz_fat + params->sz_blk - 1 ) & ~((lba_t)params->sz_blk - 1 );	/* Align data area to the erase block boundary */
	if( b_data - params->b_vol >= params->sz_vol / 2 )
		return FR_MKFS_ABORTED;	/* Too small volume? */
	n_clst = (uint32_t)(( params->sz_vol - ( b_data - params->b_vol )) / params->sz_au );	/* Number of clusters */
	if( n_clst < 16 )
		return FR_MKFS_ABORTED;			/* Too few clusters? */
	if( n_clst > MAX_EXFAT )
		return FR_MKFS_ABORTED;	/* Too many clusters? */

	szb_bit = ( n_clst + 7 ) / 8;								/* Size of allocation bitmap */
	clen[0] = ( szb_bit + params->sz_au * params->ss - 1 ) / ( params->sz_au * params->ss );	/* Number of allocation bitmap clusters */

	/* Create a compressed up-case table */
	sect = b_data + params->sz_au * clen[0];	/* Table start sector */
	sum = 0;							/* Table checksum to be stored in the 82 entry */
	st = 0;
	si = 0;
	i = 0;
	j = 0;
	szb_case = 0;
	do
		{
		switch( st )
			{
			case 0:
				ch = (ffs_wchar_t)ffs_wtoupper( si );	/* Get an up-case char */
				if( ch != si )
					{
					si++;
					break;		/* Store the up-case char if exist */
					}
				for( j = 1; (ffs_wchar_t)( si + j ) && (ffs_wchar_t)( si + j ) == ffs_wtoupper((ffs_wchar_t)( si + j )); j++ )	/* Get run length of no-case block */
					{}
				if( j >= 128 )
					{
					ch = 0xFFFF;
					st = 2;
					break;	/* Compress the no-case block if run is >= 128 chars */
					}
				st = 1;			/* Do not compress short run */
			/* FALLTHROUGH */
			case 1:
				ch = si++;		/* Fill the short run */
				if( --j == 0 )
					st = 0;
				break;

			default:
				ch = (ffs_wchar_t)j;
				si += (ffs_wchar_t)j;	/* Number of chars to skip */
				st = 0;
			}
		sum = xsum32( params->buf[i + 0] = (uint8_t)ch, sum );	/* Put it into the write buffer */
		sum = xsum32( params->buf[i + 1] = (uint8_t)( ch >> 8 ), sum );
		i += 2;
		szb_case += 2;
		if( si == 0 || i == params->sz_buf * params->ss )  		/* Write buffered data when buffer full or end of process */
			{
			n = ( i + params->ss - 1 ) / params->ss;
			if( disk_write( params->pdrv, params->buf, sect, n ) != RES_OK )
				return FR_DISK_ERR;
			sect += n;
			i = 0;
			}
		}
	while( si );
	clen[1] = ( szb_case + params->sz_au * params->ss - 1 ) / ( params->sz_au * params->ss );	/* Number of up-case table clusters */
	clen[2] = 1;	/* Number of root dir clusters */

	/* Initialize the allocation bitmap */
	sect = b_data;
	nsect = ( szb_bit + params->ss - 1 ) / params->ss;	/* Start of bitmap and number of bitmap sectors */
	nbit = clen[0] + clen[1] + clen[2];				/* Number of clusters in-use by system (bitmap, up-case and root-dir) */
	do
		{
		memset( params->buf, 0, params->sz_buf * params->ss );				/* Initialize bitmap buffer */
		for( i = 0; nbit != 0 && i / 8 < params->sz_buf * params->ss; params->buf[i / 8] |= 1 << ( i % 8 ), i++, nbit-- )	/* Mark used clusters */
			{}
		n = ( nsect > params->sz_buf ) ? params->sz_buf : nsect;		/* Write the buffered data */
		if( disk_write( params->pdrv, params->buf, sect, n ) != RES_OK )
			return FR_DISK_ERR;
		sect += n;
		nsect -= n;
		}
	while( nsect );

	/* Initialize the FAT */
	sect = b_fat;
	nsect = sz_fat;	/* Start of FAT and number of FAT sectors */
	j = nbit = clu = 0;
	do
		{
		memset( params->buf, 0, params->sz_buf * params->ss );
		i = 0;	/* Clear work area and reset write offset */
		if( clu == 0 )  	/* Initialize FAT [0] and FAT[1] */
			{
			st_dword( params->buf + i, 0xFFFFFFF8 );
			i += 4;
			clu++;
			st_dword( params->buf + i, 0xFFFFFFFF );
			i += 4;
			clu++;
			}
		do  			/* Create chains of bitmap, up-case and root dir */
			{
			while( nbit != 0 && i < params->sz_buf * params->ss )  	/* Create a chain */
				{
				st_dword( params->buf + i, ( nbit > 1 ) ? clu + 1 : 0xFFFFFFFF );
				i += 4;
				clu++;
				nbit--;
				}
			if( nbit == 0 && j < 3 )
				nbit = clen[j++];	/* Get next chain length */
			}
		while( nbit != 0 && i < params->sz_buf * params->ss );
		n = ( nsect > params->sz_buf ) ? params->sz_buf : nsect;	/* Write the buffered data */
		if( disk_write( params->pdrv, params->buf, sect, n ) != RES_OK )
			return FR_DISK_ERR;
		sect += n;
		nsect -= n;
		}
	while( nsect );

	/* Initialize the root directory */
	memset( params->buf, 0, params->sz_buf * params->ss );
	params->buf[SIZE_OF_DIR_ENTRY * 0 + 0] = ET_VLABEL;				/* Volume label entry (no label) */
	params->buf[SIZE_OF_DIR_ENTRY * 1 + 0] = ET_BITMAP;				/* Bitmap entry */
	st_dword( params->buf + SIZE_OF_DIR_ENTRY * 1 + 20, FIRST_VALID_CLUSTER );				/*  cluster */
	st_dword( params->buf + SIZE_OF_DIR_ENTRY * 1 + 24, szb_bit );		/*  size */
	params->buf[SIZE_OF_DIR_ENTRY * 2 + 0] = ET_UPCASE;				/* Up-case table entry */
	st_dword( params->buf + SIZE_OF_DIR_ENTRY * 2 + 4, sum );			/*  sum */
	st_dword( params->buf + SIZE_OF_DIR_ENTRY * 2 + 20, FIRST_VALID_CLUSTER + clen[0] );	/*  cluster */
	st_dword( params->buf + SIZE_OF_DIR_ENTRY * 2 + 24, szb_case );		/*  size */
	sect = b_data + params->sz_au * ( clen[0] + clen[1] );
	nsect = params->sz_au;	/* Start of the root directory and number of sectors */
	do  	/* Fill root directory sectors */
		{
		n = ( nsect > params->sz_buf ) ? params->sz_buf : nsect;
		if( disk_write( params->pdrv, params->buf, sect, n ) != RES_OK )
			return FR_DISK_ERR;
		memset( params->buf, 0, params->ss );	/* Rest of entries are filled with zero */
		sect += n;
		nsect -= n;
		}
	while( nsect );

	/* Create two set of the exFAT VBR blocks */
	sect = params->b_vol;
	for( n = 0; n < 2; n++ )
		{
		/* Main record (+0) */
		memset( params->buf, 0, params->ss );
		memcpy( params->buf + BS_JmpBoot, "\xEB\x76\x90" "EXFAT   ", 11 );	/* Boot jump code (x86), OEM name */
		st_qword( params->buf + BPB_VolOfsEx, params->b_vol );					/* Volume offset in the physical drive [sector] */
		st_qword( params->buf + BPB_TotSecEx, params->sz_vol );					/* Volume size [sector] */
		st_dword( params->buf + BPB_FatOfsEx, (uint32_t)( b_fat - params->b_vol ));	/* FAT offset [sector] */
		st_dword( params->buf + BPB_FatSzEx, sz_fat );					/* FAT size [sector] */
		st_dword( params->buf + BPB_DataOfsEx, (uint32_t)( b_data - params->b_vol ));	/* Data offset [sector] */
		st_dword( params->buf + BPB_NumClusEx, n_clst );					/* Number of clusters */
		st_dword( params->buf + BPB_RootClusEx, FIRST_VALID_CLUSTER + clen[0] + clen[1] );	/* Root dir cluster # */
		st_dword( params->buf + BPB_VolIDEx, params->vsn );						/* VSN */
		st_word( params->buf + BPB_FSVerEx, 0x100 );						/* Filesystem version (1.00) */
		for( params->buf[BPB_BytsPerSecEx] = 0, i = params->ss; i >>= 1; params->buf[BPB_BytsPerSecEx]++ )	/* Log2 of sector size [byte] */
			{}
		for( params->buf[BPB_SecPerClusEx] = 0, i = params->sz_au; i >>= 1; params->buf[BPB_SecPerClusEx]++ )	/* Log2 of cluster size [sector] */
			{}
		params->buf[BPB_NumFATsEx] = 1;					/* Number of FATs */
		params->buf[BPB_DrvNumEx] = 0x80;				/* Drive number (for int13) */
		st_word( params->buf + BS_BootCodeEx, 0xFEEB );	/* Boot code (x86) */
		st_word( params->buf + BS_55AA, 0xAA55 );			/* Signature (placed here regardless of sector size) */
		for( i = sum = 0; i < params->ss; i++ )  		/* VBR checksum */
			{
			if( i != BPB_VolFlagEx && i != BPB_VolFlagEx + 1 && i != BPB_PercInUseEx )
				sum = xsum32( params->buf[i], sum );
			}
		if( disk_write( params->pdrv, params->buf, sect++, 1 ) != RES_OK )
			return FR_DISK_ERR;
		/* Extended bootstrap record (+1..+8) */
		memset( params->buf, 0, params->ss );
		st_word( params->buf + params->ss - 2, 0xAA55 );	/* Signature (placed at end of sector) */
		for( j = 1; j < 9; j++ )
			{
			for( i = 0; i < params->ss; sum = xsum32( params->buf[i++], sum ))	/* VBR checksum */
				{}
			if( disk_write( params->pdrv, params->buf, sect++, 1 ) != RES_OK )
				return FR_DISK_ERR;
			}
		/* OEM/Reserved record (+9..+10) */
		memset( params->buf, 0, params->ss );
		for( ; j < 11; j++ )
			{
			for( i = 0; i < params->ss; sum = xsum32( params->buf[i++], sum ))	/* VBR checksum */
				{}
			if( disk_write( params->pdrv, params->buf, sect++, 1 ) != RES_OK )
				return FR_DISK_ERR;
			}
		/* Sum record (+11) */
		for( i = 0; i < params->ss; i += 4 )
			st_dword( params->buf + i, sum );		/* Fill with checksum value */
		if( disk_write( params->pdrv, params->buf, sect++, 1 ) != RES_OK )
			return FR_DISK_ERR;
		}

	return 0;
	}
/*============================================================================*/
static int FormatFAT( formatparams_t *params, uint8_t fsty, uint8_t fsopt )
	{
	static const uint16_t		cst[]	= { 1, 4, 16, 64, 256, 512, 0 };	/* Cluster size boundary for FAT volume (4Ks unit) */
	static const uint16_t		cst32[]	= { 1, 2, 4, 8, 16, 32, 0 };		/* Cluster size boundary for FAT32 volume (128Ks unit) */

	uint32_t		sz_rsv;
	uint32_t		sz_fat;
	uint32_t		sz_dir;
	lba_t			sect;
	lba_t			b_fat;
	uint32_t		n_clst;
	uint32_t		pau;
	uint32_t		nsect;
	uint32_t		n;

	unsigned int	i;
	lba_t			lba[2];

	/* Create an FAT/FAT32 volume */
	do
		{
		lba_t	b_data;

		pau = params->sz_au;
		/* Pre-determine number of clusters and FAT sub-type */
		if( fsty == FFS_FILESYSTEMTYPE_FAT32 )  	/* FAT32 volume */
			{
			if( pau == 0 )  	/* AU auto-selection */
				{
				n = (uint32_t)params->sz_vol / 0x20000;	/* Volume size in unit of 128KS */
				for( i = 0, pau = 1; cst32[i] && cst32[i] <= n; i++, pau <<= 1 )	/* Get from table */
					{}
				}
			n_clst = (uint32_t)params->sz_vol / pau;	/* Number of clusters */
			sz_fat = ( n_clst * 4 + 8 + params->ss - 1 ) / params->ss;	/* FAT size [sector] */
			sz_rsv = 32;	/* Number of reserved sectors */
			sz_dir = 0;		/* No static directory */
			if( n_clst <= MAX_FAT16 || n_clst > MAX_FAT32 )
				return FR_MKFS_ABORTED;
			}
		else  				/* FAT volume */
			{
			if( pau == 0 )  	/* au auto-selection */
				{
				n = (uint32_t)params->sz_vol / 0x1000;	/* Volume size in unit of 4KS */
				for( i = 0, pau = 1; cst[i] && cst[i] <= n; i++, pau <<= 1 )	/* Get from table */
					{}
				}
			n_clst = (uint32_t)params->sz_vol / pau;
			if( n_clst > MAX_FAT12 )
				{
				n = n_clst * 2 + 4;		/* FAT size [byte] */
				}
			else
				{
				fsty = FFS_FILESYSTEMTYPE_FAT12;
				n = ( n_clst * 3 + 1 ) / 2 + 3;	/* FAT size [byte] */
				}
			sz_fat = ( n + params->ss - 1 ) / params->ss;		/* FAT size [sector] */
			sz_rsv = 1;						/* Number of reserved sectors */
			sz_dir = (uint32_t)params->rootentries * SIZE_OF_DIR_ENTRY / params->ss;	/* Root dir size [sector] */
			}
		b_fat = params->b_vol + sz_rsv;						/* FAT base */
		b_data = b_fat + sz_fat * params->fatcopies + sz_dir;	/* Data base */

		/* Align data area to erase block boundary (for flash memory media) */
		n = (uint32_t)((( b_data + params->sz_blk - 1 ) & ~( params->sz_blk - 1 )) - b_data );	/* Sectors to next nearest from current data base */
		if( fsty == FFS_FILESYSTEMTYPE_FAT32 )  		/* FAT32: Move FAT */
			{
			sz_rsv += n;
			b_fat += n;
			}
		else  					/* FAT: Expand FAT */
			{
			if( n % params->fatcopies )  	/* Adjust fractional error if needed */
				{
				n--;
				sz_rsv++;
				b_fat++;
				}
			sz_fat += n / params->fatcopies;
			}

		/* Determine number of clusters and final check of validity of the FAT sub-type */
		if( params->sz_vol < b_data + pau * 16 - params->b_vol )
			return FR_MKFS_ABORTED;	/* Too small volume? */
		n_clst = ((uint32_t)params->sz_vol - sz_rsv - sz_fat * params->fatcopies - sz_dir ) / pau;
		if( fsty == FFS_FILESYSTEMTYPE_FAT32 )
			{
			if( n_clst <= MAX_FAT16 )  	/* Too few clusters for FAT32? */
				{
				if( params->sz_au == 0 && ( params->sz_au = pau / 2 ) != 0 )
					continue;	/* Adjust cluster size and retry */
				return FR_MKFS_ABORTED;
				}
			}
		if( fsty == FFS_FILESYSTEMTYPE_FAT16 )
			{
			if( n_clst > MAX_FAT16 )  	/* Too many clusters for FAT16 */
				{
				if( params->sz_au == 0 && ( pau * 2 ) <= 64 )
					{
					params->sz_au = pau * 2;
					continue;	/* Adjust cluster size and retry */
					}
				if(( fsopt & FFS_FILESYSTEMFORMAT_FAT32 ))
					{
					fsty = FFS_FILESYSTEMTYPE_FAT32;
					continue;	/* Switch type to FAT32 and retry */
					}
				if( params->sz_au == 0 && ( params->sz_au = pau * 2 ) <= 128 )
					continue;	/* Adjust cluster size and retry */
				return FR_MKFS_ABORTED;
				}
			if( n_clst <= MAX_FAT12 )  	/* Too few clusters for FAT16 */
				{
				if( params->sz_au == 0 && ( params->sz_au = pau * 2 ) <= 128 )
					continue;	/* Adjust cluster size and retry */
				return FR_MKFS_ABORTED;
				}
			}
		if( fsty == FFS_FILESYSTEMTYPE_FAT12 && n_clst > MAX_FAT12 )
			return FR_MKFS_ABORTED;	/* Too many clusters for FAT12 */

		/* Ok, it is the valid cluster configuration */
		break;
		}
	while( 1 );

	lba[0] = params->b_vol;
	lba[1] = params->b_vol + params->sz_vol - 1;	/* Inform storage device that the volume area may be erased */
	disk_ioctl( params->pdrv, CTRL_TRIM, lba );
	/* Create FAT VBR */
	memset( params->buf, 0, params->ss );
	memcpy( params->buf + BS_JmpBoot, "\xEB\xFE\x90" "MSDOS5.0", 11 );	/* Boot jump code (x86), OEM name */
	st_word( params->buf + BPB_BytsPerSec, params->ss );				/* Sector size [byte] */
	params->buf[BPB_SecPerClus] = (uint8_t)pau;				/* Cluster size [sector] */
	st_word( params->buf + BPB_RsvdSecCnt, (uint16_t)sz_rsv );	/* Size of reserved area */
	params->buf[BPB_NumFATs] = (uint8_t)params->fatcopies;					/* Number of FATs */
	st_word( params->buf + BPB_RootEntCnt, (uint16_t)(( fsty == FFS_FILESYSTEMTYPE_FAT32 ) ? 0 : params->rootentries ));	/* Number of root directory entries */
	if( params->sz_vol < 0x10000 )
		{
		st_word( params->buf + BPB_TotSec16, (uint16_t)params->sz_vol );	/* Volume size in 16-bit LBA */
		}
	else
		{
		st_dword( params->buf + BPB_TotSec32, (uint32_t)params->sz_vol );	/* Volume size in 32-bit LBA */
		}
	params->buf[BPB_Media] = 0xF8;							/* Media descriptor byte */
	st_word( params->buf + BPB_SecPerTrk, 63 );				/* Number of sectors per track (for int13) */
	st_word( params->buf + BPB_NumHeads, 255 );				/* Number of heads (for int13) */
	st_dword( params->buf + BPB_HiddSec, (uint32_t)params->b_vol );		/* Volume offset in the physical drive [sector] */
	if( fsty == FFS_FILESYSTEMTYPE_FAT32 )
		{
		st_dword( params->buf + BS_VolID32, params->vsn );			/* VSN */
		st_dword( params->buf + BPB_FATSz32, sz_fat );		/* FAT size [sector] */
		st_dword( params->buf + BPB_RootClus32, FIRST_VALID_CLUSTER );			/* Root directory cluster # (2) */
		st_word( params->buf + BPB_FSInfo32, 1 );				/* Offset of FSINFO sector (VBR + 1) */
		st_word( params->buf + BPB_BkBootSec32, 6 );			/* Offset of backup VBR (VBR + 6) */
		params->buf[BS_DrvNum32] = 0x80;					/* Drive number (for int13) */
		params->buf[BS_BootSig32] = 0x29;					/* Extended boot signature */
		memcpy( params->buf + BS_VolLab32, "NO NAME    " "FAT32   ", 19 );	/* Volume label, FAT signature */
		}
	else
		{
		st_dword( params->buf + BS_VolID, params->vsn );				/* VSN */
		st_word( params->buf + BPB_FATSz16, (uint16_t)sz_fat );	/* FAT size [sector] */
		params->buf[BS_DrvNum] = 0x80;						/* Drive number (for int13) */
		params->buf[BS_BootSig] = 0x29;						/* Extended boot signature */
		memcpy( params->buf + BS_VolLab, "NO NAME    " "FAT     ", 19 );	/* Volume label, FAT signature */
		}
	st_word( params->buf + BS_55AA, 0xAA55 );					/* Signature (offset is fixed here regardless of sector size) */
	if( disk_write( params->pdrv, params->buf, params->b_vol, 1 ) != RES_OK )
		return FR_DISK_ERR;	/* Write it to the VBR sector */

	/* Create FSINFO record if needed */
	if( fsty == FFS_FILESYSTEMTYPE_FAT32 )
		{
		disk_write( params->pdrv, params->buf, params->b_vol + 6, 1 );		/* Write backup VBR (VBR + 6) */
		memset( params->buf, 0, params->ss );
		st_dword( params->buf + FSI_LeadSig, 0x41615252 );
		st_dword( params->buf + FSI_StrucSig, 0x61417272 );
		st_dword( params->buf + FSI_Free_Count, n_clst - 1 );	/* Number of free clusters */
		st_dword( params->buf + FSI_Nxt_Free, FIRST_VALID_CLUSTER );			/* Last allocated cluster# */
		st_word( params->buf + BS_55AA, 0xAA55 );
		disk_write( params->pdrv, params->buf, params->b_vol + 7, 1 );		/* Write backup FSINFO (VBR + 7) */
		disk_write( params->pdrv, params->buf, params->b_vol + 1, 1 );		/* Write original FSINFO (VBR + 1) */
		}

	/* Initialize FAT area */
	memset( params->buf, 0, params->sz_buf * params->ss );
	sect = b_fat;		/* FAT start sector */
	for( i = 0; i < params->fatcopies; i++ )  			/* Initialize FATs each */
		{
		if( fsty == FFS_FILESYSTEMTYPE_FAT32 )
			{
			st_dword( params->buf + 0, 0xFFFFFFF8 );	/* FAT[0] */
			st_dword( params->buf + 4, 0xFFFFFFFF );	/* FAT[1] */
			st_dword( params->buf + 8, 0x0FFFFFFF );	/* FAT[2] (root directory) */
			}
		else
			{
			st_dword( params->buf + 0, ( fsty == FFS_FILESYSTEMTYPE_FAT12 ) ? 0xFFFFF8 : 0xFFFFFFF8 );	/* FAT[0] and FAT[1] */
			}
		nsect = sz_fat;		/* Number of FAT sectors */
		do  	/* Fill FAT sectors */
			{
			n = ( nsect > params->sz_buf ) ? params->sz_buf : nsect;
			if( disk_write( params->pdrv, params->buf, sect, (unsigned int)n ) != RES_OK )
				return FR_DISK_ERR;
			memset( params->buf, 0, params->ss );	/* Rest of FAT all are cleared */
			sect += n;
			nsect -= n;
			}
		while( nsect );
		}

	/* Initialize root directory (fill with zero) */
	nsect = ( fsty == FFS_FILESYSTEMTYPE_FAT32 ) ? pau : sz_dir;	/* Number of root directory sectors */
	do
		{
		n = ( nsect > params->sz_buf ) ? params->sz_buf : nsect;
		if( disk_write( params->pdrv, params->buf, sect, (unsigned int)n ) != RES_OK )
			return FR_DISK_ERR;
		sect += n;
		nsect -= n;
		}
	while( nsect );

	return 0;
	}
/*============================================================================*/
/* Logical drive number */
/* Format options */
/* Pointer to working buffer (null: use len bytes of heap memory) */
/* Size of working buffer [byte] */

int _mkfs_a( const ffs_char_t *pdn, int ipart, const mkfs_param_t* opt, void *work, size_t len )
	{
	static const mkfs_param_t	defopt	= { .format = FFS_FILESYSTEMFORMAT_ANY, .fatcopies = 1, .align = 0, .rootentries = 128, .clustersize = 512 };			/* Default parameter */
	uint8_t						fsopt, fsty;
	ffs_diskstatus_t			ds;

	formatparams_t				params;
	int							hasmbr;

	/* Check mounted drive and clear work area */
	params.pdrv	= FFS_FindPhysicalDriveByName( pdn );
	if( params.pdrv < 0 )
		return SetErrNo( ENODEV, -1 );	//FIXME

	// The volume is mounted...
	if( VolumeIndexIsValid( FindVolumeFromPhysicalDriveAndPartition( params.pdrv, ipart )))
		// ...it is not sensible to format it.
		return SetErrNo( EINVAL, -1 );	//FIXED

	/* Initialize the hosting physical drive */
	ds = disk_initialize( params.pdrv );
	if( ds & STA_NOINIT )
		return SetErrNo( ENODEV, -1 );	//FIXME: FR_NOT_READY
	if( ds & STA_PROTECT )
		return SetErrNo( ENODEV, -1 );	//FIXME: FR_WRITE_PROTECTED

	/* Get physical drive parameters (sz_drv, sz_blk and ss) */
	if( !opt )
		opt = &defopt;	/* Use default parameter if it is not given */
	params.sz_blk = opt->align;
	if( params.sz_blk == 0 )
		disk_ioctl( params.pdrv, GET_BLOCK_SIZE, &params.sz_blk );					/* Block size from the paramter or lower layer */
	if( params.sz_blk == 0 || params.sz_blk > 0x8000 || ( params.sz_blk & ( params.sz_blk - 1 )))
		params.sz_blk = 1;	/* Use default if the block size is invalid */
	if( disk_ioctl( params.pdrv, GET_SECTOR_SIZE, &params.ss ) != RES_OK )
		return SetErrNo( ENODEV, -1 );	//FIXME: FR_DISK_ERR
	if( params.ss > FFS_CONFIG_MAXIMUMSECTORSIZE || params.ss < FFS_CONFIG_MINIMUMSECTORSIZE || ( params.ss & ( params.ss - 1 )))
		return SetErrNo( ENODEV, -1 );	//FIXME: FR_DISK_ERR

	/* Options for FAT sub-type and FAT parameters */
	fsopt				= opt->format & FFS_FILESYSTEMFORMAT_ANY;
	params.fatcopies	= ( opt->fatcopies >= 1 && opt->fatcopies <= 2 ) ? opt->fatcopies : 1;
	params.rootentries	= ( opt->rootentries >= 1 && opt->rootentries <= 32768 && ( opt->rootentries % ( params.ss / SIZE_OF_DIR_ENTRY )) == 0 ) ? opt->rootentries : 512;
	params.sz_au		= ( opt->clustersize <= 0x1000000 && ( opt->clustersize & ( opt->clustersize - 1 )) == 0 ) ? opt->clustersize : 0;
	params.sz_au	   /= params.ss;	/* Byte --> Sector */

	/* Get working buffer */
	params.sz_buf = len / params.ss;		/* Size of working buffer [sector] */
	if( params.sz_buf == 0 )
		return SetErrNo( ENODEV, -1 );	//FIXME: FR_NOT_ENOUGH_CORE
	params.buf = (uint8_t*)work;		/* Working buffer */
	if( !params.buf )
		return SetErrNo( ENODEV, -1 );	//FIXME: FR_NOT_ENOUGH_CORE

	/* Determine where the volume to be located (b_vol, sz_vol) */
	params.b_vol = params.sz_vol = 0;

	/* Get partition location from the existing partition table */
	if( disk_read( params.pdrv, params.buf, 0, 1 ) != RES_OK )
		return SetErrNo( ENODEV, -1 );	//FIXME: FR_DISK_ERR	/* Load MBR */

	hasmbr	= ld_word( params.buf + BS_55AA ) == 0xAA55;

	if( hasmbr )
		{
		if( params.buf[MBR_Table + PTE_System] == 0xEE )  	/* GPT protective MBR? */
			{
			uint32_t	n_ent, ofs;
			uint64_t	pt_lba;
			int			i;					/* Index, Number of FATs and Number of roor dir entries */

			/* Get the partition location from GPT */
			if( disk_read( params.pdrv, params.buf, 1, 1 ) != RES_OK )
				return SetErrNo( ENODEV, -1 );	//FIXME: FR_DISK_ERR	/* Load GPT header sector (next to MBR) */
			if( !test_gpt_header( params.buf ))
				return SetErrNo( ENODEV, -1 );	//FIXME: FR_MKFS_ABORTED	/* Check if GPT header is valid */
			n_ent = ld_dword( params.buf + GPTH_PtNum );		/* Number of entries */
			pt_lba = ld_qword( params.buf + GPTH_PtOfs );	/* Table start sector */

			for( ofs = 0, i = -1; n_ent != 0; n_ent-- )  		/* Find MS Basic partition with order of ipart */
				{
				if( ofs == 0 && disk_read( params.pdrv, params.buf, pt_lba++, 1 ) != RES_OK )
					return SetErrNo( ENODEV, -1 );	//FIXME: FR_DISK_ERR	/* Get PT sector */
				if( !memcmp( params.buf + ofs + GPTE_PtGuid, GUID_MS_Basic, 16 ) && ( ++i == ipart || ipart < 0 ))  	/* MS basic data partition? */
					{
					params.b_vol = ld_qword( params.buf + ofs + GPTE_FstLba );
					params.sz_vol = ld_qword( params.buf + ofs + GPTE_LstLba ) - params.b_vol + 1;
					break;
					}
				ofs	= ( ofs + SZ_GPTE ) % params.ss;	/* Next entry */
				}
			if( n_ent == 0 )
				return SetErrNo( ENODEV, -1 );	//FIXME: FR_MKFS_ABORTED	/* Partition not found */
			ipart	= i;
			fsopt  |= 0x80;	/* Partitioning is in GPT */
			}
		else
			{
			uint8_t	*pte;

			if( ipart > MAX_MBR_ENTRIES )
				return SetErrNo( ENODEV, -1 );	//FIXME: FR_MKFS_ABORTED

			if( ipart >= 0 )
				{
				/* Get the partition location from MBR partition table */
				pte = params.buf + ( MBR_Table + ipart * SZ_PTE );
				if( pte[PTE_System] == 0 )
					return SetErrNo( ENODEV, -1 );	//FIXME: FR_MKFS_ABORTED	/* No partition? */
				}
			else
				{
				int	i;

				pte = params.buf + MBR_Table;
				for( i = 0; i < MAX_MBR_ENTRIES && pte[PTE_System] == 0; i++, pte += SZ_PTE )
					{}
				if( i >= MAX_MBR_ENTRIES )
					return SetErrNo( ENODEV, -1 );	//FIXME: FR_MKFS_ABORTED	/* No partition? */
				ipart	= i;
				}

			params.b_vol	= ld_dword( pte + PTE_StLba );	/* Get volume start sector */
			params.sz_vol	= ld_dword( pte + PTE_SizLba );	/* Get volume size */
			}
		}
	else  	/* The volume is associated with a physical drive */
		{
		/* We do not have an MBR... */
		if( ipart > 0 )
			/* ...only partition zero can be used. */
			return SetErrNo( ENODEV, -1 );	//FIXME
			
		/* If the partition was not specified, now we must set it to zero. */
		ipart	= 0;

		if( disk_ioctl( params.pdrv, GET_SECTOR_COUNT, &params.sz_vol ) != RES_OK )
			return SetErrNo( ENODEV, -1 );	//FIXME: FR_DISK_ERR
		}

	if( params.sz_vol < 128 )
		return SetErrNo( ENODEV, -1 );	//FIXME: FR_MKFS_ABORTED	/* Check if volume size is >=128s */

	/* Now start to create an FAT volume at b_vol and sz_vol */

	do  	/* Pre-determine the FAT type */
		{
		if( fsopt & FFS_FILESYSTEMFORMAT_EXFAT )  	/* exFAT possible? */
			{
			if(( fsopt & FFS_FILESYSTEMFORMAT_ANY ) == FFS_FILESYSTEMFORMAT_EXFAT || params.sz_vol >= 0x4000000 || params.sz_au > 128 )  	/* exFAT only, vol >= 64MS or sz_au > 128S ? */
				{
				fsty = FFS_FILESYSTEMTYPE_EXFAT;
				break;
				}
			}
		if( params.sz_vol >= 0x100000000 )
			return SetErrNo( ENODEV, -1 );	//FIXME: FR_MKFS_ABORTED	/* Too large volume for FAT/FAT32 */
		if( params.sz_au > 128 )
			params.sz_au = 128;	/* Invalid AU for FAT/FAT32? */
		if( fsopt & FFS_FILESYSTEMFORMAT_FAT32 )  	/* FAT32 possible? */
			{
			if( !( fsopt & FFS_FILESYSTEMFORMAT_FAT ))  	/* no-FAT? */
				{
				fsty = FFS_FILESYSTEMTYPE_FAT32;
				break;
				}
			}
		if( !( fsopt & FFS_FILESYSTEMFORMAT_FAT ))
			return SetErrNo( EINVAL, -1 );	//FIXED	/* no-FAT? */
		fsty = FFS_FILESYSTEMTYPE_FAT16;
		}
	while( 0 );

	params.vsn = (uint32_t)params.sz_vol + get_fattime();	/* VSN generated from current time and partitiion size */

	if( fsty == FFS_FILESYSTEMTYPE_EXFAT )  	/* Create an exFAT volume */
		FormatEXFAT( &params );
	else
		FormatFAT( &params, fsty, fsopt );

	/* A FAT volume has been created here */

	/* Update partition information */
	if( hasmbr && !( fsopt & 0x80 ))  	/* Is the partition in MBR? */
		{
		uint8_t	sys;

		/* Determine system ID in the MBR partition table */
		if( fsty == FFS_FILESYSTEMTYPE_EXFAT )
			sys = FFS_SYSTYPE_EXFAT;		/* exFAT */
		else if( fsty == FFS_FILESYSTEMTYPE_FAT32 )
			sys = FFS_SYSTYPE_FAT32X;		/* FAT32X */
		else if( params.sz_vol >= 0x10000 )
			sys = FFS_SYSTYPE_FAT1216L;		/* FAT12/16 (large) */
		else if( fsty == FFS_FILESYSTEMTYPE_FAT16 )
			sys = FFS_SYSTYPE_FAT16;		/* FAT16 */
		else
			sys = FFS_SYSTYPE_FAT12;		/* FAT12 */

		/* Update system ID in the partition table */
		if( disk_read( params.pdrv, params.buf, 0, 1 ) != RES_OK )
			return SetErrNo( ENODEV, -1 );	//FIXME: FR_DISK_ERR	/* Read the MBR */
		params.buf[MBR_Table + ipart * SZ_PTE + PTE_System] = sys;			/* Set system ID */
		if( disk_write( params.pdrv, params.buf, 0, 1 ) != RES_OK )
			return SetErrNo( ENODEV, -1 );	//FIXME: FR_DISK_ERR	/* Write it back to the MBR */
		}

	if( disk_ioctl( params.pdrv, CTRL_SYNC, 0 ) != RES_OK )
		return SetErrNo( ENODEV, -1 );	//FIXME: FR_DISK_ERR

	return 0;
	}
/*----------------------------------------------------------------------------*/
int mkfs( const ffs_char_t *pdname, const mkfs_param_t *opt, void *work, size_t len )
	{
	ffs_char_t	pdn[6];
	int			part;
	int			result;

	/* We are testing the existence of the mutex here to save time in the case
	   the system was alread initialized. It is tested again inside a critical
	   section in function Initialize() to ensure data integrity. */
	if( FFS_SystemMutex == NULL )
		Initialize();

	/* This operation is safe to do before acquiring the mutex because it only
	   uses local data.*/
	if( ParsePhysicalDriveName( pdname, pdn, sizeof pdn, &part ) < 0 )
		return SetErrNo( ENODEV, -1 ); //FIXME
	
	if( !FFS_TakeMutex( FFS_SystemMutex ))
		return SetErrNo( EBUSY, -1 ); //FIXME
	
	result	= _mkfs_a( pdn, part, opt, work, len );
	
	FFS_ReleaseMutex( FFS_SystemMutex );

	return result;
	}

/*============================================================================*/
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Create Partition Table on the Physical Drive                               */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
int _fdisk_a( const ffs_char_t *pdn, const lba_t ptbl[], void *work )	/* Physical drive number */ /* Pointer to the size table for each partitions */ /* Pointer to the working buffer */
	{
	int					pdrv;
	ffs_diskstatus_t	stat;

	/* The requested drive is not attached... */
	pdrv	= FFS_FindPhysicalDriveByName( pdn );
	if( pdrv < 0 )
		return SetErrNo( ENODEV, -1 );

	// There is at least one volume mounted on this drive...
	if( VolumeIndexIsValid( FindVolumeFromPhysicalDriveAndPartition( pdrv, -1 )))
		// ...it is not sensible to partition it.
		return SetErrNo( EBUSY, -1 );	//FIXME: FR_INVALID_DRIVE

	/* Initialize the physical drive */
	stat = disk_initialize( pdrv );
	if( stat & STA_NOINIT )
		return SetErrNo( ENODEV, -1 );	//FIXME: FR_NOT_READY
	if( stat & STA_PROTECT )
		return SetErrNo( ENODEV, -1 );	//FIXME: FR_WRITE_PROTECTED

	return create_partition( pdrv, ptbl, 0x07, work );	/* Create partitions (system ID is temporary setting and determined by mkfs) */
	}
/*----------------------------------------------------------------------------*/
int fdisk( const ffs_char_t *pdname, const lba_t ptbl[], void *work )	/* Physical drive number */ /* Pointer to the size table for each partitions */ /* Pointer to the working buffer */
	{
	int			part;
	int			result;
	ffs_char_t	pdn[6];

	/* We are testing the existence of the mutex here to save time in the case
	   the system was alread initialized. It is tested again inside a critical
	   section in function Initialize() to ensure data integrity. */
	if( FFS_SystemMutex == NULL )
		Initialize();

	if( work == NULL )
		return SetErrNo( ENODEV, -1 );	//FIXME: FR_NOT_ENOUGH_CORE

	/* This operation is safe to do before acquiring the mutex because it only
	   uses local data.*/
	if( ParsePhysicalDriveName( pdname, pdn, sizeof pdn, &part ) < 0 )
		return SetErrNo( ENODEV, -1 ); //FIXME
	
	/* It was supplied a partition number, but 'fdisk' operates on all partitions... */
	if( part >= 0 )
		return SetErrNo( EINVAL, -1 ); //FIXED
	
	if( !FFS_TakeMutex( FFS_SystemMutex ))
		return SetErrNo( EBUSY, -1 ); //FIXME

	result	= _fdisk_a( pdn, ptbl, work );

	FFS_ReleaseMutex( FFS_SystemMutex );

	return result;
	}
/*============================================================================*/
/*----------------------------------------------------------------------------*/
/* Set Active Codepage for the Path Name                                 */
/*----------------------------------------------------------------------------*/
/*============================================================================*/
int setcp( uint16_t cp )	/* Value to be set as active code page */
	{
	static const uint16_t       validcp[22]	= {   437,   720,   737,   771,   775,   850,   852,   855,   857,   860,   861,   862,   863,   864,   865,   866,   869,   932,   936,   949,   950, 0 };
	static const uint8_t *const tables[22]	= { Ct437, Ct720, Ct737, Ct771, Ct775, Ct850, Ct852, Ct855, Ct857, Ct860, Ct861, Ct862, Ct863, Ct864, Ct865, Ct866, Ct869, Dc932, Dc936, Dc949, Dc950, 0 };
	unsigned int	i;
    DeclareIntState( s );

	for( i = 0; validcp[i] != 0 && validcp[i] != cp; i++ )	/* Find the code page */
		{}

	if( validcp[i] != cp )
		return SetErrNo( EINVAL, -1 );	//FIXED	(FR_INVALID_PARAMETER)	/* Not found? */

	/* We could take the system mutex here, but it would be a huge waste of
	   time for a so short time of use. It is better just to use a critical section. */
	FFS_EnterCriticalSection();

	FFS_CodePage	= cp;
	if( cp >= 900 )		/* DBCS */
		{
		FFS_ExCvt	= 0;
		FFS_DbcTbl	= tables[i];
		}
	else  				/* SBCS */
		{
		FFS_ExCvt	= tables[i];
		FFS_DbcTbl	= 0;
		}

	FFS_ExitCriticalSection( s );

	return 0;
	}
/*============================================================================*/
